Страницы

Поиск по вопросам

четверг, 19 декабря 2019 г.

Как реализован цикл for? Почему `for x in a: x=1` не меняет `a` список

#python #python_faq


>>> a = [1,2,3,4,5,]
>>> for x in a:   x=1
>>> 
>>> a
[1, 2, 3, 4, 5]
>>> 


Вопрос - почему а не равен [1,1,1,1,1]??
Неужели в цикле создается копия для каждого х?

как сделать "правильно" я понимаю. Просто интересен механизм цикла ...ведь для списка
работает:

>>> a = [[1],[2],[3]]
>>> for x in a: x.append(33)

>>> 
>>> a
[[1, 33], [2, 33], [3, 33]]


а присваивание тоже "не работает" со ссылкой ...

>>> a
[[1, 33], [2, 33], [3, 33]]
>>> for x in a:  x = [22]

>>> 
>>> a
[[1, 33], [2, 33], [3, 33]]
>>> 


Ладно - последние два примера, которые мне вообще кажутся странными:

>>> a = [[1],[2],[3]]
>>> a
[[1], [2], [3]]
>>> for x in a: x = x +[11]

>>> a
[[1], [2], [3]]
>>> for x in a: x+=[11]

>>> a
[[1, 11], [2, 11], [3, 11]]
>>> 


где это и как объясняется?
    


Ответы

Ответ 1



Q: Как реализован цикл for? for x in a: x = 1 можно себе представить как: it = iter(a) while True: try: x = next(it) except StopIteration: break else: x = 1 то есть: получить итератор из переданной коллекции и по одному обойти все элементы, присваивая каждое значение переменной x (это описывает поведение, не фактическую реализацию). Q: почему а не равен [1,1,1,1,1]? a список содержит ссылки на самые обычные объекты. Объект никак не меняется от того, что какие-то списки могут ссылки на него содержать. Поэтому a не равен [1,1,1,1,1] по той же причине что и x = 2 x = 1 не делает 2 == 1. И даже если бы мы изменяемые объекты здесь использовали, такие как списки, из: x = [1] x = [2] не следует, что [1] == [2]. Подробнее: на первой итерации x = next(it) заставляет x имя ссылаться на тот же объект, что и a[0], то есть x is a[0] в этот момент. Затем x = 1 заставляет x имя ссылаться на единичку. Можно себе это представлять, как ярлык с одного предмета на другой переместили: c одного объекта a=1 на другой объект ярлык перевесили a=2 . От этого изначальный объект никак не меняется, иначе даже после for x in L: pass у вас стал бы весь список последнему элементу равен L = [5,5,5,5,5]. два примера, которые мне вообще кажутся странными: Для примера a = [[1],[2],[3]] циклы с x = x + [1] против x += [1] неравнозначны. Первый случай аналогичен предыдущим (простое присваивание): сумма x + [1] создаёт новый список, к которому прикрепляется имя x — ярлык со старого списка снимается и переносится на новый. Второй пример использует += операцию, которая для списков по месту изменение проводит — это равнозначно x.extend([1]), что не создаёт новый список, а добавляет элементы из аргумента к старому списку. В последнем случае и до и после x имя продолжает ссылаться на один и тот же объект (тот же список). где это и как объясняется? Чтобы узнать, что += делает, просто вызовите help('+=') в Питон REPL. Конкретно для изменяемых последовательностей (таких как списки) поведение s += t равнозначно s.extend(t).

Ответ 2



Каждый раз в переменную записывается значение из списка (не копия), но при записи другого значения в эту же переменную, значение записывается именно в переменную, а не в список. Правильно делать так: a = [1,2,3,4,5] for i, x in enumerate(a): a[i] = 1 print(a) # [1, 1, 1, 1, 1] Можно удостовериться, что в переменную попадает не копия, а сам объект из списка. Только список лучше брать из чисел больше 256, т.к. для Python каждое число от -5 до 256 является одним и тем же объектом, т.е. >>> a = 256 >>> b = 256 >>> a == b True >>> a is b True # один и тот же объект >>> a = 257 >>> b = 257 >>> a == b True >>> a is b False # разные объекты Внимание: данное поведение не гарантируется, в разных реализациях и версиях языка внутренняя реализация объектов целых чисел может различаться. Для примера берем числа от 1000 до 1004: >>> a = list(range(1000, 1005)) >>> a [1000, 1001, 1002, 1003, 1004] >>> for i, x in enumerate(a): ... print(a[i] is x) True True True True True Видим, что в переменной x каждый раз оказывается тот же объект, что и в списке по индексу. Обновление. По поводу append. В самом первом примере вы изменяете значение в переменной, никак не связанной со списком, по сути просто в переменную записываете новое значение (переменная сначала "указывала" на объект внутри списка, а после присваивания начала указывать на другой объект, список при этом не изменяется). В случае с append, вы изменяете сам объект (его "внутренности"), полученный из списка (как я уже показал выше, при итерации по списку в переменную попадает сам объект, а не его копия). Т.к. это тот же самый объект, что лежит в списке, то видим, что и в самом списке объект меняется. Обновление 2 = и += это разные операторы, работающие по-разному. += может изменять исходный объект (если объект изменяемый, например если это список, но не число, ну и в зависимости от реализации метода __iadd__, если это какой-то свой объект), а = явно заменяет значение переменной, но не изменяет объект, который лежал в переменной до присваивания.

Комментариев нет:

Отправить комментарий