无限嵌套列表中究竟发生了什么?

Gan*_*ndi 31 python list infinite

可以在Python中创建无限的嵌套列表.这很清楚,虽然不受欢迎,但绝对没用,这是一个众所周知的事实.

>>> a = [0]
>>> a[0] = a
>>> a
[[...]]
>>> a[0] == a
True
Run Code Online (Sandbox Code Playgroud)

我的问题是,这里发生了什么:

>>> 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
>>> 
Run Code Online (Sandbox Code Playgroud)

哪个方面更深,当我试图理解它时,我感觉更像是我的大脑会爆炸.我看,一个包含b,包含一个等等......

现在我对这一个问题.我们这里真的有两个列表,还是只有一个?这样的东西如何存储在内存中?什么可能是一个目的,使程序员实现这样奇怪的东西?

请不要把这个问题视为超级严肃.不要忘记,编程有时会很有趣.

Ste*_*ven 26

免责声明:我不使用Python,所以我说的一些事情可能是错的.Python专家,随时纠正我.

好问题.我认为中心的误解(如果我甚至不能称之为;你完全合理的是你如何达到你所使用的思维过程)你提示你提出问题是这样的:

当我写的b[0] = a,这并不意味着a b.它意味着b包含指向指向的东西的引用a.

变量ab自己甚至没有"东西"自己,而他们自己也仅仅是指针,否则在内存匿名的"东西".

引用的概念是非编程世界的一次重大飞跃,所以让我们在考虑到这一点的情况下逐步完成您的程序:

>>> a = [0]
Run Code Online (Sandbox Code Playgroud)

您创建一个恰好包含其中某些内容的列表(暂时忽略它).重要的是它是一个列表.该列表存储在内存中.假设它存储在内存位置1001中.然后,赋值=创建一个变量a,编程语言允许您稍后使用.此时,内存中有一些列表对象,并且可以使用名称访问它a.

>>> b = [0]
Run Code Online (Sandbox Code Playgroud)

这也是一样的b.有一个新列表存储在内存位置1002中.编程语言创建一个引用b,您可以使用它来引用内存位置,进而引用列表对象.

>>> a[0], b[0] = b, a
Run Code Online (Sandbox Code Playgroud)

这有两个相同的东西,所以让我们关注一个:a[0] = b.这样做很漂亮.它首先评估相等的右侧,查看变量b并获取内存中的相应对象(内存对象#1002),因为b它是对它的引用.左侧发生的事情同样很奇特.a是一个指向列表的变量(内存对象#1001),但是内存对象#1001本身有许多自己的引用.而不是那些具有ab,你使用的名称的引用,这些引用有数字索引,如0.所以,现在,它的作用是a提取内存对象#1001,它是一堆索引引用,并且它转到索引为0的引用(之前,此引用指向实际数字0,这是你在行中执行的操作1)然后重新引用参考(即,存储器对象#1001中的第一个和唯一的引用)到等式右边的东西评估的内容.所以现在,对象#1001的第0个引用指向对象#1002.

>>> a
[[[...]]]
>>> b
[[[...]]]
Run Code Online (Sandbox Code Playgroud)

这只是编程语言所做的好看.当你只是要求它进行评估时a,它会拉出内存对象(位置#1001处的列表),使用它自己的魔法来检测它是无限的并呈现自己.

>>> a == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
Run Code Online (Sandbox Code Playgroud)

这个陈述的失败与Python的比较方式有关.将对象与自身进行比较时,它会立即求值为true.当您比较并反对另一个对象时,它使用"magic"来确定相等性是真还是假.对于Python中的列表,它会查看每个列表中的每个项目并检查它们是否相等(反过来使用项目自己的相等检查方法).所以,当你尝试a == b.它的作用是首先挖掘b(对象#1002)和(对象#1001)然后意识到它们在内存中是不同的东西,所以转到它的递归列表检查器.它通过迭代两个列表来实现这一点.对象#1001具有一个索引为0的元素,其指向对象#1002.对象#1002具有一个索引为0的元素,其指向对象#1001.因此,程序得出结论,如果对象#1001和#1002的所有引用都指向相同的东西,则它们是相等的,如果#1002(#1001的唯一引用指向的是什么),则#1001(#1002的唯一引用指向的是)同一件事情.这种平等检查永远不会停止.在任何不停止的列表中都会发生同样的事情.你可以做c = [0]; d = [0]; c[0] = d; d[0] = c并且a == c会引发同样的错误.

>>> a[0] == b
True
Run Code Online (Sandbox Code Playgroud)

正如我在前一段中暗示的那样,这立即解析为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
Run Code Online (Sandbox Code Playgroud)

这又回到了一个错误,因为a[0][0]最终指向对象#1001.身份检查失败并返回到递归内容检查,该检查永远不会结束.

>>> a[0][0][0] == b
True
Run Code Online (Sandbox Code Playgroud)

再次a[0][0][0]指向对象#1002,同样如此b.跳过递归检查,比较立即返回true.


更高级别的jibber jabber与您的特定代码段没有直接关系:

  • 由于所有引用都引用了其他对象,即使存在看似"无限"的嵌套,由a(我称之为对象#1001)引用的对象和引用的对象b(#1002)也是内存中的大小相同.而且这个大小实际上非常小,因为它们都是指向相应其他内存位置的列表.
  • 值得注意的是,在不那么"慷慨"的语言中,只有在它们指向的内存对象在两个引用指向内存中相同位置的意义上相同时才将两个引用与==返回进行比较.Java就是一个例子.在这些语言中出现的风格约定是在对象本身(对于Java,通常称为Java)上定义方法/函数来进行自定义相等性测试.Python为列表开箱即用.我特别不了解Python,但至少在Ruby中,从某种意义上来说,它是重载的,它实际上调用了一个名为on 的方法(你可以覆盖).理论上,除了布尔值之外,没有什么可以阻止你做出返回的东西.true equals()==someobject == otherobject==someobjectsomeobject == otherobject


phi*_*mue 11

怀疑发生了以下情况:

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].


ron*_*akg 10

a[0]是指bb[0]a.这是一个循环参考.正如glglgl所提到的,当你使用==operator时,它会进行值的比较.

试试这个,这可能会让事情变得更加清晰 -

>>> id(a)
4299818696
>>> id(b)
4299818768
>>> id(a[0])
4299818768
>>> 
>>> id(b[0])
4299818696
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案.它解释了非常简单,如何存储两个列表. (2认同)

Shi*_*ish 7

我明白了,一个包含b,包含一个

它们不相互包含 - 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
Run Code Online (Sandbox Code Playgroud)

[0]的数量无关紧要,因为您可以根据需要进行多次列表查找 - 重要的是在示例#1和#3(以及所有奇数查找)中,您说的是" B等于B",此时python比较内存地址并发现它们是相同的,所以说是的.使用示例#2(以及所有偶数查找),您说"A等于B",python看到它们是不同的内存地址,然后尝试将整个(无限)数据结构加载到内存中以执行更多操作深度比较.