Возможно создание бесконечного вложенного списка в Python. Это ясно и, хотя и не популярно и определенно не полезно, является известным фактом.
>>> a = [0]
>>> a[0] = a
>>> a
[[...]]
>>> a[0] == a
True
Мой вопрос: что здесь происходит:
>>> a = [0]
>>> b = [0]
>>> a[0], b[0] = b, a
>>> a
[[[...]]]
>>> b
[[[...]]]
>>> a == b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
>>> a[0] == b
True
>>> 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][0] == b
True
>>>
Что каждый путь глубже, когда я пытаюсь понять это, я чувствую себя намного больше, как мой мозг собирается взорваться. Я вижу, что a содержит b, содержащий a и т.д....
Теперь мои вопросы об этом. У нас действительно есть два списка, или только один? Как такая вещь хранится в памяти? Что может быть целью, позволяющей программистам реализовать что-то настолько странное, как это?
Пожалуйста, не рассматривайте этот вопрос супер-серьезным. И не забывайте, что программирование иногда может быть забавным.
Ответ 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), и объект, упомянутый be b
(# 1002) имеют одинаковый размер в памяти. И этот размер на самом деле невероятно мал, поскольку все они являются списками, указывающими на соответствующие другие ячейки памяти.
- Также стоит отметить, что на менее "щедрых" языках сравнение двух ссылок с
==
возвращает true
только, если объекты памяти, на которые они указывают, одинаковы в том смысле, что обе ссылки указывают на одно и то же место в памяти. Java - пример этого. Стилистическое соглашение, появившееся на таких языках, заключается в том, чтобы определить метод/функцию для самих объектов (для Java, это условно называется equals()
), чтобы выполнить выборочное тестирование равенства. Python делает это из списка для списков. Я не знаю, в частности, о Python, но, по крайней мере, в Ruby, ==
перегружен в том смысле, что когда вы делаете someobject == otherobject
, он на самом деле вызывает метод под названием ==
на someobject
(который вы можете перезаписать). Теоретически, вам нечего было бы заставить someobject == otherobject
возвращать нечто, отличное от логического.
Ответ 2
Я подозреваю, что следующее:
a[0]==b
: Python просматривает значение a[0]
и находит какую-то ссылку на b
, поэтому он говорит True
.
a[0][0]==b
: Python смотрит вверх a[0]
, находит b
и теперь смотрит вверх a[0][0]
, который есть (поскольку a[0]
содержит b
) b[0]
. Теперь он видит, что b[0]
содержит некоторую ссылку на a
, что не совсем то же самое, что b
. Таким образом, python должен сравнивать элементы, что означает, что он должен сравнивать a[0]
с b[0]
. Теперь начинается бесконечная рекурсия...
Обратите внимание, что это работает только потому, что Python фактически не копирует список при назначении a[0]=b
. Python скорее создает ссылку на b
, которая хранится в a[0]
.
Ответ 3
a[0]
относится к b
, а b[0]
относится к a
. Это круговая ссылка. Как упоминал glglgl, при попытке оператора ==
он пытается сравнить значения.
Попробуйте это, что может сделать вещи более ясными -
>>> id(a)
4299818696
>>> id(b)
4299818768
>>> id(a[0])
4299818768
>>>
>>> id(b[0])
4299818696
Ответ 4
Я вижу, что a содержит b, содержащий a
Они не содержат друг друга как таковые - A является ссылкой на список, первое, что в этом списке является ссылкой на B, и наоборот
>>> a[0] == b
True
>>> 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][0] == b
True
Здесь число [0] не имеет значения, так как вы можете делать столько запросов списка, сколько захотите, - важно то, что в примерах # 1 и # 3 (и все нечетные числа поисков) вы говорите "B равно B", в этот момент python сравнивает адреса памяти и видит, что они одно и то же, так что да. В примере # 2 (и всех поисковых запросах) вы говорите: "A равно B", python видит, что они разные адреса памяти, а затем пытается загрузить всю (бесконечную) структуру данных в память, глубинное сравнение.
Ответ 5
Это два списка. Сначала создайте их:
a = [0]
b = [0]
И затем вы назначаете каждый из них первому элементу другого:
a[0], b[0] = b, a
Итак, вы можете сказать
a[0] is b
и
b[0] is a
которая является той же самой ситуацией, что и первый пример, но уровень obe глубже.
Кроме того, вы не сравниваете идентичность (is
), а для равенства (==
). Это приводит к попытке сравнить их - глубоко внутри, что приводит к рекурсии.