2011-10-06 59 views
29

可以在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 
>>> 

每個更深刻,當我試圖去了解它,我覺得更像是我的大腦會爆炸。我看到,一個包含b,包含一個等...

現在我對這個問題。我們真的有兩個列表嗎?或者只有一個?這樣的事情是如何存儲在內存中的?有什麼可以讓程序員執行這樣奇怪的事情?

請不要把這個問題看得超嚴重。別忘了,有時編程可能很有趣。

+1

這真是一個有趣的功能。 – phimuemue

+0

真棒問題。我非常喜歡Python的這個功能,儘管我從來沒有找到它的用處。如果有人能夠提出這個特性的實際應用,那將是非常好的。或者寫一個模塊來生成包含所有列表的列表:P – andronikus

+1

@andronikus:http://xkcd.com/468/ –

回答

24

免責聲明:我不使用Python,所以有些事情我說的可能是錯的。 Python專家,隨時糾正我。

偉大的問題。我認爲中央誤解(如果我甚至不能稱呼它,它是完全合理的,你如何到達你所使用的思維過程)您遇到提示你要問的問題是這樣的:

當我寫b[0] = a時,並不意味着ab。這意味着b包含一個引用,指向a指向的內容。

變量ab自己本身甚至不是「事物」,它們本身也僅僅是指向內存中其他匿名「事物」的指針。

引用的概念是從非編程世界的一次重大飛躍,讓我們通過你的程序與此步驟記:

>>> 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本身具有多個自己的引用。這些參考文獻的數值不是像ab這樣的名稱,而是具有數字索引,如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(#1001的唯一參考點)和#1001(#1002的唯一參考點)是否相同,則程序得出結論:如果對象#1001和#1002的所有引用都指向相同的東西,一樣的東西。這種平等檢查永遠不會停止。同樣的事情會發生在任何不停止的列表中。你可以做c = [0]; d = [0]; c[0] = d; d[0] = ca == c會引發同樣的錯誤。

>>> a[0] == b 
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。


更高級別的胡言亂語胡言亂語不直接關係到你的特定代碼片段:

  • 由於所有有所指的其他對象的引用,即使有這似乎是「無限」的嵌套,由a(正如我所說的對象#1001)引用的對象和被稱爲b(#1002)的對象在內存中都是相同的大小。而且這個尺寸實際上非常小,因爲它們都是指向其他存儲位置的列表。
  • 另外值得一注意,在更短的「大手筆」的語言,比較==回報true只有如果它們指向的內存對象是在這個意義上,這兩個引用指向內存中的相同點相同的兩個引用。 Java就是這樣的一個例子。已經出現在這種語言中的文體慣例是定義對象本身的方法/函數(對於Java,通常稱爲equals())來執行自定義相等性測試。 Python爲列表開箱即用。我不太瞭解Python,但至少在Ruby中,==負載過重,因爲在執行someobject == otherobject時,它實際上調用someobject(可以覆蓋)的==方法。從理論上講,讓someobject == otherobject返回布爾值以外的內容沒有任何阻礙。
+0

很好的回答。真的,我無能爲力,只是接受它而已。 – Gandi

+0

+1一個不錯的和詳細的答案。我唯一可能抱怨的是'[0]'在Python中被稱爲* list *,而不是*數組*。也有[數組](http://docs.python.org/library/array.html),但它們不像列表那樣有助於循環引用。 –

+0

@SvenMarnach:謝謝你指出。我會快速編輯,以便將來的人不會感到困惑。爲什麼數組不支持循環引用?他們克隆重新分配還是什麼? –

11

嫌疑會發生以下情況:

a[0]==b:Python的查找值a[0],發現某種參考b,所以它說True

a[0][0]==b:Python的查找a[0],發現b現在查找a[0][0],這一點,(因爲a[0]持有bb[0]。現在看到,b[0]a持有某種引用,與b不完全相同。所以python必須比較元素,這意味着它必須比較a[0]b[0]。現在,無限遞歸開始......

注意這僅僅是因爲分配a[0]=b時,Python不實際複製該列表。相反,Python會創建一個b的引用,存儲在a[0]中。

+0

這是一個很好的解釋。我想,我開始明白這一點。 – Gandi

4

這些是兩個列表。首先,創建它們:

a = [0] 
b = [0] 

然後,可以爲每個一個到另一個的第一個元素:

a[0], b[0] = b, a 

因此可以這樣說

a[0] is b 

b[0] is a 

這是一樣的情況離子作爲第一個例子,但服從更深。

此外,你不比較的身份(is),但對於平等(==)。這導致嘗試比較它們 - 深入內部,導致遞歸的原因。

+0

用'is'好東西。我沒有想過用這種方式比較它。 – Gandi

7

我看到,一個包含B,包含

他們不互相牽制這樣 - 一個是一個列表的引用,在此列表中的第一件事情是一個參考對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比較內存地址a看到他們是同一件事,所以說是的。通過例子#2(以及所有的偶數查找),你會說「是A等於B」,python認爲它們是不同的內存地址,然後嘗試將整個(無限)數據結構加載到內存中,深度比較。

10

a[0]指的是bb[0]指的是a。這是一個循環參考。正如glglgl所提到的,當您嘗試==運算符時,它會嘗試比較值。

試試這個,這可能會使事情更清晰 -

>>> id(a) 
4299818696 
>>> id(b) 
4299818768 
>>> id(a[0]) 
4299818768 
>>> 
>>> id(b[0]) 
4299818696 
+2

這是一個很好的答案。它解釋非常簡單,兩個列表如何存儲。 – Gandi