Переменная цикла — это отдельное имя, а не ячейка списка, поэтому строка price = price * 2 внутри for перенаправляет имя на новое число и до самого списка не доходит. Чтобы список действительно изменился, в него надо писать: по индексу через enumerate, пересобрать его списковым включением или заменить содержимое срезом prices[:] = ....
Вот код, с которого начинается вопрос.
prices = [120, 250, 90]
for price in prices:
price = price * 2
print(prices)
[120, 250, 90]
Исключения нет, traceback не падает, программа отработала — и не сделала ничего. Это худший тип бага: молчаливый. Python выполнил ровно то, что написано, просто написано было не то.
Почему в Python в цикле for меняю переменную, а список не меняется?
Потому что for price in prices не выдаёт тебе ячейку списка. На каждой итерации Python берёт очередной элемент и привязывает к нему имя price — как если бы ты вручную написал price = prices[0], затем price = prices[1] и так далее. После этой привязки имя и ячейка списка живут отдельно друг от друга. Присваивание price = price * 2 меняет только то, куда смотрит имя price, а список о существовании этого имени ничего не знает.
Убедиться можно за две строки: имя price спокойно переживает цикл и хранит последний элемент.
prices = [120, 250, 90]
for price in prices:
pass
print(price)
90
Если бы price был «окошком в список», после цикла такой переменной не существовало бы. А она есть — обычное имя.
Переменная цикла — это новое имя, а не ячейка списка
Формулировка «в Python for x in list — присваивание не работает» описывает симптом точно, но объясняет его наоборот. Присваивание как раз работает, и работает безупречно: оно связывает имя слева со значением справа. Просто имя слева — не часть списка.
В Python имя — это ярлык, наклеенный на объект. prices — ярлык на объект-список, price — ярлык на объект-число, который в этот момент лежит в списке. Когда ты пишешь price = price * 2, Python вычисляет новое число 240 и переклеивает ярлык price на него. Содержимого списка это не касается: там по-прежнему 120.
Вот почему изменения в цикле не сохраняются: Python записывает их в имя, а не в список. Ты каждый раз создаёшь новое значение и теряешь его на следующей итерации.
Так что жалоба «в Python в цикле for меняю переменную, а список не меняется» описывает не баг языка, а разницу между именем и объектом.
Присваивание против изменения: одна строка, два разных действия
В Python переприсваивание и изменение объекта — разница, которая решает всё; это главное, что стоит унести из статьи. Переприсваивание (имя = ...) трогает только имя. Изменение (объект[i] = ..., объект.append(...), объект.sort()) трогает сам объект, и это видно всем, кто на него ссылается.
| Строка в теле цикла |
Что реально происходит |
Внешний список |
price = price * 2 |
имя price переклеивается на новое число |
не меняется |
price += 100 |
у чисел это то же самое, что price = price + 100 |
не меняется |
prices[i] = price * 2 |
запись в ячейку списка по индексу |
меняется |
row.append(0) |
метод правит сам объект-список |
меняется |
row = row + [0] |
создаётся новый список, имя переклеивается на него |
не меняется |
row += [0] |
для списка += — это расширение на месте |
меняется |
Обрати внимание на две последние строки: row = row + [0] и row += [0] выглядят синонимами, но делают разное. Для чисел += действительно эквивалентен пересозданию, а для списка расширяет существующий объект.
Почему в Python вложенный список меняется в цикле, а числа нет?
Потому что элементы разного типа ведут себя по-разному. Число неизменяемо: с ним нельзя сделать ничего, кроме как вычислить новое число и привязать к имени. Список изменяем: у него есть методы, которые правят содержимое на месте, а переменная цикла указывает ровно на тот же объект, что лежит внутри внешнего списка. Поэтому row.append(0) виден снаружи, а price = price * 2 — нет. Один и тот же цикл, противоположные исходы.
rows = [[1, 2], [3, 4]]
for row in rows:
row.append(0)
print(rows)
[[1, 2, 0], [3, 4, 0]]
А теперь то же самое, но через переприсваивание:
rows = [[1, 2], [3, 4]]
for row in rows:
row = row + [0]
print(rows)
[[1, 2], [3, 4]]
Тот же цикл, тот же список, разница в одном символе — и противоположный результат. Из-за этой пары поведение Python кажется произвольным, пока не разведёшь в голове имена и объекты.
Тот же принцип объясняет, почему цикл по списку словарей из JSON правит данные без всяких ухищрений: словарь изменяем, поэтому item["итого"] = ... внутри for item in items доходит до самого списка. А вот обращение к внешнему списку по имени поля — items["цена"] вместо items[0]["цена"] — даёт совсем другую ошибку, list indices must be integers or slices, not str.
Кстати, изменяемость списка — это ровно то свойство, из-за которого его нельзя положить в множество или сделать ключом словаря. Там оно вылезает громко, как TypeError: unhashable type: 'list', а здесь — молча.
Как изменить элементы списка в цикле Python?
Рабочих способов три, и отличаются они не стилем, а тем, останется ли список тем же объектом. Запись по индексу и срезовое присваивание правят существующий список; списковое включение создаёт новый и вешает на него старое имя. Если на список ссылается кто-то ещё — например, он пришёл аргументом в функцию, — эта разница решающая.
Способ 1: изменить элемент по индексу в цикле
prices = [120, 250, 90]
for i, price in enumerate(prices):
prices[i] = price * 2
print(prices)
[240, 500, 180]
Чтобы в Python изменить элемент по индексу в цикле, нужен сам индекс — его даёт enumerate, отдавая на каждой итерации пару «индекс, элемент». Индекс и есть недостающая ячейка: prices[i] = ... пишет в список, а не в имя. Справка — в разделе enumerate. Без него получится for i in range(len(prices)): работает, но читается хуже.
Потренироваться на индексах можно на задаче «Где большие буквы»: там нужны позиции заглавных букв, то есть индексы, а не сами символы.
Способ 2: пересобрать список включением
prices = [120, 250, 90]
prices = [price * 2 for price in prices]
print(prices)
[240, 500, 180]
Самый частый и читаемый вариант, когда правило одно на все элементы. Никакой магии: справа собирается новый список, слева имя prices переклеивается на него. Формы включений — в разделе Comprehensions.
Задача «Только неубывающие» — как раз про пересборку: оставить в новом списке только элементы не меньше предыдущего.
Способ 3: срезовое присваивание, когда нужен тот же объект
cart = [120, 250, 90]
backup = cart
cart[:] = [price * 2 for price in cart]
print(backup)
[240, 500, 180]
cart[:] = ... не создаёт новый список — он заменяет содержимое существующего. Поэтому имя backup, которое смотрит на тот же объект, видит изменения. Сравни с обычным присваиванием:
cart = [120, 250, 90]
backup = cart
cart = [price * 2 for price in cart]
print(backup)
[120, 250, 90]
| Способ |
Код |
Тот же объект списка |
Когда брать |
| Запись по индексу |
for i, x in enumerate(a): a[i] = f(x) |
да |
нужна логика с if, continue, доступом к соседям |
| Списковое включение |
a = [f(x) for x in a] |
нет, новый список |
обычный случай: одно правило на все элементы |
| Срезовое присваивание |
a[:] = [f(x) for x in a] |
да |
на список ссылается кто-то ещё |
Почему append() внутри цикла работает, а присваивание — нет?
Потому что append() — это метод, который меняет сам объект-список, а присваивание — операция над именем. Когда ты добавляешь элементы в отдельный список-накопитель, ты вызываешь метод на объекте, и результат остаётся в объекте. Противоречия с предыдущими примерами нет: работает то, что трогает объект, и не работает то, что трогает только имя.
result = []
for price in [120, 250, 90]:
result.append(price * 2)
print(result)
[240, 500, 180]
Это четвёртый рабочий способ, и для новичка он часто самый понятный: исходный список не трогаем вообще, а ответ собираем рядом.
Строки и числа неизменяемы: то же правило, другой симптом
Со строками ловушка убедительнее: там вызывается метод, а значит кажется, что объект меняется.
words = ["хлеб", "молоко"]
for word in words:
word = word.upper()
print(words)
['хлеб', 'молоко']
word.upper() не меняет строку, а возвращает новую. Строки в Python неизменяемы: любой «изменяющий» метод строки конструирует другой объект. Дальше срабатывает знакомый механизм — результат привязывается к имени word и теряется на следующей итерации. Чинится так же: words[i] = word.upper() через enumerate или words = [w.upper() for w in words].
У кортежа то же свойство доведено до конца: у него нет ни append, ни записи по индексу, и попытка point[0] = 999 немедленно даёт TypeError: 'tuple' object does not support item assignment. Кортеж хотя бы честно ругается — список и число молчат.
Почему то же самое ломает функции, которым передали список?
Потому что параметр функции — тоже просто имя. При вызове double_all(cart) имя prices внутри функции привязывается к тому же объекту, что и cart снаружи. Если внутри написать prices = [...], имя переклеится на новый список, а внешний cart останется прежним. Это ровно та же ошибка, что и с переменной цикла, просто на другом уровне.
def double_all(prices):
prices = [p * 2 for p in prices]
cart = [120, 250, 90]
double_all(cart)
print(cart)
[120, 250, 90]
Срезовое присваивание чинит это одной правкой:
def double_all(prices):
prices[:] = [p * 2 for p in prices]
cart = [120, 250, 90]
double_all(cart)
print(cart)
[240, 500, 180]
Второй честный вариант — вернуть новый список и присвоить его на вызывающей стороне: cart = double_all(cart). Он предпочтительнее, когда функция не обязана ничего портить у вызывающего.
Частые ошибки
- Добавили
enumerate, но пишут всё равно в переменную. for i, price in enumerate(prices): price = price * 2 не меняет список — индекс получен, но не использован. Правильно: prices[i] = price * 2.
- Считают, что
+= всегда работает на месте. Для списка row += [0] расширяет объект, для числа price += 100 создаёт новое число. Одна и та же запись, разные последствия.
- Присваивают новый список внутри функции.
prices = [...] в теле функции не виден снаружи. Нужно либо prices[:] = ..., либо return и присваивание у вызывающего.
- Путают
backup = prices с копией. Это второе имя на тот же объект: prices[0] = 999 изменит и backup. Настоящая копия — prices.copy() или prices[:]; подводные камни собраны в разделе Shallow and deep list copies.
- Создают сетку через
[row] * 3. Так получаются три ссылки на один список, и grid[0][0] = 5 даёт [[5, 0], [5, 0], [5, 0]]. Нужно [[0, 0] for _ in range(3)].
- Удаляют элементы прямо во время итерации. Это другой баг с другим механизмом: цикл идёт по индексу, а список сжимается, и часть элементов пропускается — разбор в статье про удаление из списка в цикле.
- Ждут, что цикл по
dict.items() изменит словарь. for name, count in totals.items(): count = count * 10 не меняет ничего — работает только totals[name] = count * 10.
Мини-резюме
for x in a привязывает к x очередной элемент; x — обычное имя, а не ячейка списка.
- Присваивание меняет имя, изменение меняет объект — от этого зависит, увидит ли список правку.
- Числа и строки неизменяемы, поэтому с ними «работает» только пересборка списка.
- Вложенный список меняется в цикле, а числа нет — потому что у списка есть методы, правящие его на месте.
- Три рабочих фикса:
a[i] = ... через enumerate, a = [f(x) for x in a], a[:] = [f(x) for x in a].
- Срезовое присваивание — единственный из трёх, который сохраняет тот же объект: бери его, когда список пришёл в функцию аргументом.
- Внутри функции
prices = [...] не влияет на аргумент вызывающего; либо prices[:] = ..., либо return.
Переменная цикла — это отдельное имя, а не ячейка списка, поэтому строка
price = price * 2внутриforперенаправляет имя на новое число и до самого списка не доходит. Чтобы список действительно изменился, в него надо писать: по индексу черезenumerate, пересобрать его списковым включением или заменить содержимое срезомprices[:] = ....Вот код, с которого начинается вопрос.
prices = [120, 250, 90] for price in prices: price = price * 2 print(prices)Исключения нет, traceback не падает, программа отработала — и не сделала ничего. Это худший тип бага: молчаливый. Python выполнил ровно то, что написано, просто написано было не то.
Почему в Python в цикле for меняю переменную, а список не меняется?
Потому что
for price in pricesне выдаёт тебе ячейку списка. На каждой итерации Python берёт очередной элемент и привязывает к нему имяprice— как если бы ты вручную написалprice = prices[0], затемprice = prices[1]и так далее. После этой привязки имя и ячейка списка живут отдельно друг от друга. Присваиваниеprice = price * 2меняет только то, куда смотрит имяprice, а список о существовании этого имени ничего не знает.Убедиться можно за две строки: имя
priceспокойно переживает цикл и хранит последний элемент.prices = [120, 250, 90] for price in prices: pass print(price)Если бы
priceбыл «окошком в список», после цикла такой переменной не существовало бы. А она есть — обычное имя.Переменная цикла — это новое имя, а не ячейка списка
Формулировка «в Python
for x in list— присваивание не работает» описывает симптом точно, но объясняет его наоборот. Присваивание как раз работает, и работает безупречно: оно связывает имя слева со значением справа. Просто имя слева — не часть списка.В Python имя — это ярлык, наклеенный на объект.
prices— ярлык на объект-список,price— ярлык на объект-число, который в этот момент лежит в списке. Когда ты пишешьprice = price * 2, Python вычисляет новое число240и переклеивает ярлыкpriceна него. Содержимого списка это не касается: там по-прежнему120.Вот почему изменения в цикле не сохраняются: Python записывает их в имя, а не в список. Ты каждый раз создаёшь новое значение и теряешь его на следующей итерации.
Так что жалоба «в Python в цикле for меняю переменную, а список не меняется» описывает не баг языка, а разницу между именем и объектом.
Присваивание против изменения: одна строка, два разных действия
В Python переприсваивание и изменение объекта — разница, которая решает всё; это главное, что стоит унести из статьи. Переприсваивание (
имя = ...) трогает только имя. Изменение (объект[i] = ...,объект.append(...),объект.sort()) трогает сам объект, и это видно всем, кто на него ссылается.price = price * 2priceпереклеивается на новое числоprice += 100price = price + 100prices[i] = price * 2row.append(0)row = row + [0]row += [0]+=— это расширение на местеОбрати внимание на две последние строки:
row = row + [0]иrow += [0]выглядят синонимами, но делают разное. Для чисел+=действительно эквивалентен пересозданию, а для списка расширяет существующий объект.Почему в Python вложенный список меняется в цикле, а числа нет?
Потому что элементы разного типа ведут себя по-разному. Число неизменяемо: с ним нельзя сделать ничего, кроме как вычислить новое число и привязать к имени. Список изменяем: у него есть методы, которые правят содержимое на месте, а переменная цикла указывает ровно на тот же объект, что лежит внутри внешнего списка. Поэтому
row.append(0)виден снаружи, аprice = price * 2— нет. Один и тот же цикл, противоположные исходы.rows = [[1, 2], [3, 4]] for row in rows: row.append(0) print(rows)А теперь то же самое, но через переприсваивание:
rows = [[1, 2], [3, 4]] for row in rows: row = row + [0] print(rows)Тот же цикл, тот же список, разница в одном символе — и противоположный результат. Из-за этой пары поведение Python кажется произвольным, пока не разведёшь в голове имена и объекты.
Тот же принцип объясняет, почему цикл по списку словарей из JSON правит данные без всяких ухищрений: словарь изменяем, поэтому
item["итого"] = ...внутриfor item in itemsдоходит до самого списка. А вот обращение к внешнему списку по имени поля —items["цена"]вместоitems[0]["цена"]— даёт совсем другую ошибку, list indices must be integers or slices, not str.Кстати, изменяемость списка — это ровно то свойство, из-за которого его нельзя положить в множество или сделать ключом словаря. Там оно вылезает громко, как TypeError: unhashable type: 'list', а здесь — молча.
Как изменить элементы списка в цикле Python?
Рабочих способов три, и отличаются они не стилем, а тем, останется ли список тем же объектом. Запись по индексу и срезовое присваивание правят существующий список; списковое включение создаёт новый и вешает на него старое имя. Если на список ссылается кто-то ещё — например, он пришёл аргументом в функцию, — эта разница решающая.
Способ 1: изменить элемент по индексу в цикле
prices = [120, 250, 90] for i, price in enumerate(prices): prices[i] = price * 2 print(prices)Чтобы в Python изменить элемент по индексу в цикле, нужен сам индекс — его даёт
enumerate, отдавая на каждой итерации пару «индекс, элемент». Индекс и есть недостающая ячейка:prices[i] = ...пишет в список, а не в имя. Справка — в разделе enumerate. Без него получитсяfor i in range(len(prices)): работает, но читается хуже.Потренироваться на индексах можно на задаче «Где большие буквы»: там нужны позиции заглавных букв, то есть индексы, а не сами символы.
Способ 2: пересобрать список включением
prices = [120, 250, 90] prices = [price * 2 for price in prices] print(prices)Самый частый и читаемый вариант, когда правило одно на все элементы. Никакой магии: справа собирается новый список, слева имя
pricesпереклеивается на него. Формы включений — в разделе Comprehensions.Задача «Только неубывающие» — как раз про пересборку: оставить в новом списке только элементы не меньше предыдущего.
Способ 3: срезовое присваивание, когда нужен тот же объект
cart = [120, 250, 90] backup = cart cart[:] = [price * 2 for price in cart] print(backup)cart[:] = ...не создаёт новый список — он заменяет содержимое существующего. Поэтому имяbackup, которое смотрит на тот же объект, видит изменения. Сравни с обычным присваиванием:cart = [120, 250, 90] backup = cart cart = [price * 2 for price in cart] print(backup)for i, x in enumerate(a): a[i] = f(x)if,continue, доступом к соседямa = [f(x) for x in a]a[:] = [f(x) for x in a]Почему append() внутри цикла работает, а присваивание — нет?
Потому что
append()— это метод, который меняет сам объект-список, а присваивание — операция над именем. Когда ты добавляешь элементы в отдельный список-накопитель, ты вызываешь метод на объекте, и результат остаётся в объекте. Противоречия с предыдущими примерами нет: работает то, что трогает объект, и не работает то, что трогает только имя.result = [] for price in [120, 250, 90]: result.append(price * 2) print(result)Это четвёртый рабочий способ, и для новичка он часто самый понятный: исходный список не трогаем вообще, а ответ собираем рядом.
Строки и числа неизменяемы: то же правило, другой симптом
Со строками ловушка убедительнее: там вызывается метод, а значит кажется, что объект меняется.
words = ["хлеб", "молоко"] for word in words: word = word.upper() print(words)word.upper()не меняет строку, а возвращает новую. Строки в Python неизменяемы: любой «изменяющий» метод строки конструирует другой объект. Дальше срабатывает знакомый механизм — результат привязывается к имениwordи теряется на следующей итерации. Чинится так же:words[i] = word.upper()черезenumerateилиwords = [w.upper() for w in words].У кортежа то же свойство доведено до конца: у него нет ни
append, ни записи по индексу, и попыткаpoint[0] = 999немедленно даётTypeError: 'tuple' object does not support item assignment. Кортеж хотя бы честно ругается — список и число молчат.Почему то же самое ломает функции, которым передали список?
Потому что параметр функции — тоже просто имя. При вызове
double_all(cart)имяpricesвнутри функции привязывается к тому же объекту, что иcartснаружи. Если внутри написатьprices = [...], имя переклеится на новый список, а внешнийcartостанется прежним. Это ровно та же ошибка, что и с переменной цикла, просто на другом уровне.def double_all(prices): prices = [p * 2 for p in prices] cart = [120, 250, 90] double_all(cart) print(cart)Срезовое присваивание чинит это одной правкой:
def double_all(prices): prices[:] = [p * 2 for p in prices] cart = [120, 250, 90] double_all(cart) print(cart)Второй честный вариант — вернуть новый список и присвоить его на вызывающей стороне:
cart = double_all(cart). Он предпочтительнее, когда функция не обязана ничего портить у вызывающего.Частые ошибки
enumerate, но пишут всё равно в переменную.for i, price in enumerate(prices): price = price * 2не меняет список — индекс получен, но не использован. Правильно:prices[i] = price * 2.+=всегда работает на месте. Для спискаrow += [0]расширяет объект, для числаprice += 100создаёт новое число. Одна и та же запись, разные последствия.prices = [...]в теле функции не виден снаружи. Нужно либоprices[:] = ..., либоreturnи присваивание у вызывающего.backup = pricesс копией. Это второе имя на тот же объект:prices[0] = 999изменит иbackup. Настоящая копия —prices.copy()илиprices[:]; подводные камни собраны в разделе Shallow and deep list copies.[row] * 3. Так получаются три ссылки на один список, иgrid[0][0] = 5даёт[[5, 0], [5, 0], [5, 0]]. Нужно[[0, 0] for _ in range(3)].dict.items()изменит словарь.for name, count in totals.items(): count = count * 10не меняет ничего — работает толькоtotals[name] = count * 10.Мини-резюме
for x in aпривязывает кxочередной элемент;x— обычное имя, а не ячейка списка.a[i] = ...черезenumerate,a = [f(x) for x in a],a[:] = [f(x) for x in a].prices = [...]не влияет на аргумент вызывающего; либоprices[:] = ..., либоreturn.