Ответ 1
Отказ от ответственности: я не использую Python, поэтому некоторые вещи, которые я говорю, могут быть неправильными. Python, не стесняйтесь меня исправлять.
Отличный вопрос. Я думаю, что центральное заблуждение (если я даже не могу назвать это так: совершенно разумно, как вы пришли к мыслительному процессу, который вы использовали), у вас есть подсказка, чтобы задать вопрос:
Когда я пишу b[0] = a
, это не означает, что a
находится в b
. Это означает, что b
содержит ссылку, указывающую на то, что указывает a
.
Переменные a
и b
сами по себе сами не являются "вещами", и сами они также являются просто указателями на анонимные "вещи" в памяти.
Концепция ссылок - это серьезный скачок от мира, не связанного с программированием, поэтому давайте рассмотрим это в вашей программе:
>>> a = [0]
Вы создаете список, который имеет что-то в нем (на этот раз игнорируйте это). Важно то, что это список. Этот список сохраняется в памяти. Пусть говорят, что он хранится в ячейке памяти 1001. Затем присваивание =
создает переменную a
, которую язык программирования позволяет вам использовать позже. На данный момент есть некоторый объект списка в памяти и ссылка на него, с которой вы можете получить доступ с именем a
.
>>> b = [0]
Это делает то же самое для b
. Существует новый список, который хранится в ячейке памяти 1002. Язык программирования создает ссылку b
, которую вы можете использовать для ссылки на ячейку памяти и, в свою очередь, объект списка.
>>> a[0], b[0] = b, a
Это делает две вещи одинаковыми, поэтому давайте сосредоточимся на одном: a[0] = b
. То, что это делает, очень причудливо. Сначала он оценивает правую часть равенства, видит переменную b
и выбирает соответствующий объект в памяти (объект памяти # 1002), так как b
является ссылкой на него. То, что происходит с левой стороны, одинаково причудливо. a
- это переменная, которая указывает на список (объект памяти # 1001), но сам объект памяти # 1001 имеет несколько собственных ссылок. Вместо тех ссылок, которые имеют имена типа a
и b
, которые вы используете, эти ссылки имеют числовые индексы, такие как 0
. Итак, теперь это то, что это a
вызывает объект памяти # 1001, который является кучей индексированных ссылок, и он переходит к ссылке с индексом 0 (ранее эта ссылка указывала на фактическое число 0
, которое это то, что вы делали в строке 1), а затем переустанавливает эту ссылку (т.е. первую и единственную ссылку в объекте памяти # 1001) на то, что оценивает вещь в правой части уравнения. Итак, теперь объект # 1001 0-я ссылка указывает на объект # 1002.
>>> a
[[[...]]]
>>> b
[[[...]]]
Это просто фантазия, сделанная языком программирования. Когда вы просто попросите его оценить a
, он вытащит объект памяти (список в местоположении # 1001), обнаруживает, используя свою собственную магию, что он бесконечен и отображает себя как таковой.
>>> a == b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
Ошибка этого оператора связана с тем, как Python выполняет сравнения. Когда вы сравниваете объект с самим собой, он сразу же оценивает значение true. Когда вы сравниваете и возражаете против другого объекта, он использует "магию", чтобы определить, должно ли равенство быть истинным или ложным. В случае списков в Python он просматривает каждый элемент в каждом списке и проверяет, равны ли они (в свою очередь, используя собственные методы проверки элементов). Итак, при попытке a == b
. То, что он делает, сначала выкапывает b (объект # 1002) и (объект # 1001), а затем понимает, что они разные вещи в памяти, поэтому переходит к его рекурсивной проверке списка. Он делает это, итерируя через два списка. Объект # 1001 имеет один элемент с индексом 0, который указывает на объект # 1002. Объект # 1002 имеет один элемент с индексом 0, который указывает на объект # 1001. Таким образом, программа делает вывод о том, что объекты # 1001 и # 1002 равны, если все их ссылки указывают на одно и то же, ergo, если # 1002 (что означает только 1001 опорные точки) и # 1001 (что означает только опорные точки # 1002) тоже самое. Эта проверка равенства никогда не прекратится. То же самое произойдет в любом списке, который не остановится. Вы могли бы сделать c = [0]; d = [0]; c[0] = d; d[0] = c
и a == c
, чтобы вызвать ту же ошибку.
>>> a[0] == b
True
Как я уже указывал в предыдущем абзаце, это немедленно разрешает true, потому что Python принимает ярлык. Не нужно сравнивать содержимое списка, потому что a[0]
указывает на объект # 1002 и b
указывает на объект # 1002. Python обнаруживает, что они идентичны в буквальном смысле (они - одна и та же вещь) и даже не утруждают себя проверкой содержимого.
>>> a[0][0] == b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
Это возвращается к ошибке, потому что a[0][0]
заканчивается, указывая на объект # 1001. Проверка удостоверения не выполняется и возвращается к рекурсивной проверке содержимого, которая никогда не заканчивается.
>>> a[0][0][0] == b
True
Еще раз, a[0][0][0]
указывает на объект # 1002, как и на b
. Рекурсивная проверка пропускается, и сравнение немедленно возвращает true.
jabber jibber более высокого уровня напрямую не связан с вашим конкретным фрагментом кода:
- Так как все есть ссылки, относящиеся к другим объектам, хотя существует то, что кажется "бесконечным" вложением, объект, на который ссылается
a
(как я назвал объект # 1001), и объект, упомянутый beb
(# 1002) имеют одинаковый размер в памяти. И этот размер на самом деле невероятно мал, поскольку все они являются списками, указывающими на соответствующие другие ячейки памяти. - Также стоит отметить, что на менее "щедрых" языках сравнение двух ссылок с
==
возвращаетtrue
только, если объекты памяти, на которые они указывают, одинаковы в том смысле, что обе ссылки указывают на одно и то же место в памяти. Java - пример этого. Стилистическое соглашение, появившееся на таких языках, заключается в том, чтобы определить метод/функцию для самих объектов (для Java, это условно называетсяequals()
), чтобы выполнить выборочное тестирование равенства. Python делает это из списка для списков. Я не знаю, в частности, о Python, но, по крайней мере, в Ruby,==
перегружен в том смысле, что когда вы делаетеsomeobject == otherobject
, он на самом деле вызывает метод под названием==
наsomeobject
(который вы можете перезаписать). Теоретически, вам нечего было бы заставитьsomeobject == otherobject
возвращать нечто, отличное от логического.