python - ¿Qué está sucediendo exactamente en las listas anidadas infinitas?
infinite (5)
Es posible crear una lista anidada infinita en Python. Eso está claro y, aunque no es popular y definitivamente no es útil, es un hecho conocido.
>>> a = [0]
>>> a[0] = a
>>> a
[[...]]
>>> a[0] == a
True
Mi pregunta es, ¿qué está pasando aquí?
>>> 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
>>>
En cada sentido, cuando intento comprenderlo, siento que mi cerebro va a explotar. Veo, que a contiene b, que contiene a y así sucesivamente ...
Ahora mis preguntas sobre este. ¿Realmente tenemos dos listas aquí, o solo una? ¿Cómo una cosa como esta se almacena en la memoria? ¿Cuál podría ser el propósito de permitir que los programadores implementen algo tan extraño como este?
Por favor, no trates esta pregunta muy seria. Y no olvides, que la programación puede ser divertida a veces.
Ya veo, que a contiene b, que contiene a
No se contienen entre sí como tales: A es una referencia a una lista, lo primero de esta lista es una referencia a B y viceversa
>>> 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
El número de [0] aquí no importa, ya que puede hacer tantas búsquedas de listas como desee; lo que importa es que en el ejemplo # 1 y # 3 (y en todos los números impares de búsquedas) está diciendo " es B igual a B ", en cuyo punto python compara las direcciones de memoria y ve que son lo mismo, por lo que dice que sí. Con el ejemplo # 2 (y todas las búsquedas parciales) que dice "es A igual a B", Python ve que son direcciones de memoria diferentes y luego intenta cargar la estructura de datos completa (infinita) en la memoria para hacer más ininformación. comparacion de profundidad
Descargo de responsabilidad: no uso Python, por lo que algunas cosas que digo pueden estar mal. Expertos en Python, siéntete libre de corregirme.
Gran pregunta Creo que la idea falsa central (si es que no puedo llamarlo así; es perfectamente razonable cómo llegó al proceso de pensamiento que usó) que le pide que haga la pregunta:
Cuando escribo b[0] = a
, no significa que a
esté en b
. Significa que b
incluye una referencia que apunta a lo que apunta.
Las variables b
sí mismas ni siquiera son "cosas" en sí mismas, y ellas mismas también son simplemente indicadores de "cosas" anónimas en la memoria.
El concepto de referencias es un gran salto desde el mundo no programado, así que avancemos en su programa con esto en mente:
>>> a = [0]
Creas una lista que tiene algo dentro (ignora eso por ahora). Lo que importa es que es una lista. Esa lista se almacena en la memoria. Digamos que está almacenado en la ubicación de memoria 1001. Luego, la asignación =
crea una variable a
que el lenguaje de programación le permite usar más adelante. En este punto, hay un objeto de lista en la memoria y una referencia a la que puede acceder con el nombre a
.
>>> b = [0]
Esto hace lo mismo para b
. Hay una nueva lista que se almacena en la ubicación de memoria 1002. El lenguaje de programación crea una referencia b
que puede usar para referirse a la ubicación de la memoria y, a su vez, al objeto de la lista.
>>> a[0], b[0] = b, a
Esto hace dos cosas que son idénticas, así que concentrémonos en una: a[0] = b
. Lo que esto hace es bastante elegante. Primero evalúa el lado derecho de la igualdad, ve la variable b
y busca el objeto correspondiente en la memoria (objeto de memoria # 1002) ya que b
es una referencia a él. Lo que sucede en el lado izquierdo es igualmente elegante. a
es una variable que apunta a una lista (objeto de memoria # 1001), pero el objeto de memoria # 1001 en sí tiene una serie de referencias propias. En lugar de que esas referencias tengan nombres como a
y b
, que usa, esas referencias tienen índices numéricos como 0
. Entonces, ahora, lo que esto hace es a
objeto de memoria de extracción # 1001, que es una pila de referencias indexadas, y va a la referencia con el índice 0 (anteriormente, esta referencia apuntaba al número real 0
, que es algo que hizo en la línea 1) y luego devuelve esa referencia (es decir, la primera y única referencia en el objeto de memoria # 1001) a lo que la cosa en el lado derecho de la ecuación evalúa. Así que ahora, los puntos de referencia 0 del objeto # 1001 al objeto # 1002.
>>> a
[[[...]]]
>>> b
[[[...]]]
Esto es sólo una fantasía hecha por el lenguaje de programación. Cuando solo le pide que evalúe a
, extrae el objeto de memoria (la lista en la ubicación # 1001), detecta utilizando su propia magia que es infinita y se representa a sí misma como tal.
>>> a == b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
El fallo de esta declaración tiene que ver con cómo Python hace comparaciones. Cuando comparas un objeto con él mismo, inmediatamente se evalúa como verdadero. Cuando se compara y se opone a otro objeto, usa "magia" para determinar si la igualdad debe ser verdadera o falsa. En el caso de las listas en Python, examina cada elemento de cada lista y verifica si son iguales (a su vez, utilizando los propios métodos de control de igualdad de los elementos). Entonces, cuando intentas a == b
. Lo que hace es primero desenterrar b (objeto # 1002) y a (objeto # 1001) y luego se da cuenta de que son cosas diferentes en la memoria, así que va a su verificador de lista recursivo. Lo hace iterando a través de las dos listas. El objeto # 1001 tiene un elemento con índice 0 que apunta al objeto # 1002. El objeto # 1002 tiene un elemento con índice 0 que apunta al objeto # 1001. Por lo tanto, el programa concluye que los objetos # 1001 y # 1002 son iguales si todas sus referencias apuntan a la misma cosa, ergo si # 1002 (a lo que apuntan los únicos puntos de referencia de # 1001) y # 1001 (a qué son los únicos puntos de referencia de # 1002) la misma cosa. Este control de igualdad nunca puede parar. Lo mismo sucedería en cualquier lista que no se detenga. Podrías hacer c = [0]; d = [0]; c[0] = d; d[0] = c
c = [0]; d = [0]; c[0] = d; d[0] = c
c = [0]; d = [0]; c[0] = d; d[0] = c
y a == c
generaría el mismo error.
>>> a[0] == b
True
Como indiqué en el párrafo anterior, esto se resuelve inmediatamente a verdadero porque Python toma un atajo. No es necesario comparar el contenido de la lista porque a[0]
apunta al objeto # 1002 y b
apunta al objeto # 1002. Python detecta que son idénticos en el sentido literal (son la misma "cosa") y ni siquiera se molesta en verificar el contenido.
>>> a[0][0] == b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
Esto vuelve a ser un error porque a[0][0]
termina apuntando al objeto # 1001. La verificación de identidad falla y recurre a la verificación de contenido recursiva, que nunca termina.
>>> a[0][0][0] == b
True
Una vez más, a[0][0][0]
apunta al objeto # 1002, al igual que b
. La verificación recursiva se omite y la comparación devuelve inmediatamente verdadero.
Jibber Jabber de nivel superior no relacionado directamente con su fragmento de código específico:
- Dado que todo lo que hay son referencias a otros objetos, aunque parezca ser un anidamiento "infinito", el objeto al que hace referencia a (como he llamado objeto # 1001) y el objeto al que se hace referencia es
b
(# 1002 ) son ambos del mismo tamaño en la memoria. Y ese tamaño es increíblemente pequeño, ya que todas son listas que apuntan a las respectivas ubicaciones de memoria. - También vale la pena notar que en lenguajes menos "generosos", comparar dos referencias con
==
devuelvetrue
solo si los objetos de memoria a los que apuntan son iguales en el sentido de que ambas referencias apuntan al mismo lugar en la memoria. Java es un ejemplo de esto. La convención estilística que ha surgido en tales lenguajes es definir un método / función en los propios objetos (para Java, se llama convencionalmenteequals()
) para realizar pruebas de igualdad personalizadas. Python hace esto fuera de la caja para las listas. No sé sobre Python en particular, pero al menos en Ruby,==
está sobrecargado en el sentido de que cuando hacessomeobject == otherobject
, en realidad se llama un método llamado==
ensomeobject
(que puedes sobrescribir). En teoría, no habría nada que lesomeobject == otherobject
hacer quesomeobject == otherobject
devuelva algo que no sea un booleano.
Estas son dos listas. Primero, los creas:
a = [0]
b = [0]
Y luego, asigna cada uno al primer elemento del otro:
a[0], b[0] = b, a
Así que puedes decir
a[0] is b
y
b[0] is a
que es la misma situación que el primer ejemplo, pero a nivel más profundo.
Además, no se compara la identidad ( is
), sino la igualdad ( ==
). Esto lleva a un intento de compararlos, profundamente adentro, lo que lleva a una recursión.
a[0]
refiere a b
y b[0]
refiere a a
. Esta es una referencia circular. Como mencionó glglgl, cuando usa el operador ==
, hace la comparación de valores.
Prueba esto, lo que podría aclarar las cosas.
>>> id(a)
4299818696
>>> id(b)
4299818768
>>> id(a[0])
4299818768
>>>
>>> id(b[0])
4299818696
Sospecho que sucede lo siguiente:
a[0]==b
: Python busca el valor a[0]
y encuentra algún tipo de referencia a b
, por lo que dice True
.
a[0][0]==b
: Python busca a a[0]
, encuentra b
y ahora busca a a[0][0]
, que es (ya que a[0]
tiene b
) b[0]
. Ahora ve, que b[0]
tiene algún tipo de referencia a a
, que no es exactamente lo mismo que b
. Así que Python tiene que comparar elementos, lo que significa que tiene que comparar a[0]
con b[0]
. Ahora, la recursión infinita comienza ...
Tenga en cuenta que esto funciona solo porque Python no copia la lista cuando asigna a[0]=b
. Python crea una referencia a b
que se almacena en a[0]
.