2013-10-29 74 views
2

所以我有一個4×3嵌套列表(4行,3列),我已嵌套如下:更新元素將不會在Python列表工作

>>> c= [x[:] for x in [[None]*3]*4] 
>>> print c 
[[None, None, None], 
[None, None, None], 
[None, None, None], 
[None, None, None]] 

我已經以這種方式初始化了我的嵌套列表,因爲this other SO question很好地解釋了爲什麼其他一些方法不起作用。 (如c = [[無] * 3] * 4)

現在我想的第一行中的所有元素更新爲0,即我想設置的所有元素在

c[0] to 0. So I tried the following: 
>>> for x in c[0]: x = 0 
... 
>>> c 
[[None, None, None], [None, None, None], [None, None, None], [None, None, None]] 
>>> 

,你可以看,元素沒有更新。然而 以下工作:

>>> c[0] = [0 for x in c[0]] 
>>> 
>>> c 
[[0, 0, 0], [None, None, None], [None, None, None], [None, None, None]] 

而且我幾乎可以肯定,它會因爲我創建0的一個新的列表,並將其分配給C [0]。

無論如何,我接着繼續使用for循環,並嘗試將第一列(即每行的第一個元素)更新爲0並且工作正常。

>>> for x in c: x[0] = 0 
... 
>>> c 
[[0, None, None], [0, None, None], [0, None, None], [0, None, None]] 

我明白,這對循環更新是從前面的for循環更新不同的,因爲第一個試圖遍歷一個元素,而這一次遍歷列表,只是訪問每個列表的第一個元素。

我敢肯定,我錯過了一些關於名稱指向其他名字的東西,但我不能把手指放在這裏的確切問題。有人可以幫忙嗎?

+0

整數是不可變的,所以:'a = b = 1; b + = 1'不會影響'a'。 –

+0

我明白,整數和字符串在Python中是不可變的,但我恐怕不明白這個問題的含義。你能否詳細說明一下? – keithxm23

+0

爲什麼不使用索引,通常在迭代時不能更改列表,而是在枚舉(c [0]):c [0] [n] = 0中使用'for n,_ –

回答

3
for x in c[0]: x = 0 

在這個循環中,您實際上正在創建對該列表中存在的整數的新引用,然後更改這些新引用。

由於整數是不變的,所以原始參考不會受到影響。另外,因爲賦值不是就地操作,所以這也不會影響可變對象。

>>> a = b = 1 
>>> b += 1  # in-place operation, this will work differently for mutable objects 
>>> a, b  # a is still unchanged 
(1, 2) 

作業的操作不會影響到可變對象,以及:

>>> x = y = [1, 1] 
>>> x = 2 # `x` now points to a new object, number of references to [1, 1] decreased by 1 
>>> x, y 
(2, [1, 1]) 

但就地操作將:

>>> x = y = [1, 1] 
>>> x.append(2) 
>>> x, y 
([1, 1, 2], [1, 1, 2]) 

所以,這上面的循環實際上相當於:

x = c[0] 
x = 0 #this won't affect `c[0]` 
x = c[1] 
x = 0 
... 

在這個循環中,你實際上改變了C [0]指向一個新的列表對象:

>>> c= [x[:] for x in [[None]*3]*4] 
>>> c= [x[:] for x in [[None]*3]*4] 
>>> print id(c[0]) 
45488072 
>>> c[0] = [0 for x in c[0]] 
>>> print id(c[0])    #c[0] is a new list object 
45495944 
+0

輝煌。這現在很有意義。對於我來說,理解這一點的關鍵是(正如Mark Mikofski在評論中提到的那樣),x僅僅是對c的一個「視圖」,正如你所提到的那樣,x正在創造新的對這些值的新引用名單。感謝人們! – keithxm23

1

在每種情況下,x參考的東西。

如果它是對None的引用,則將創建int(0)的實例並將x切換爲引用該實例。所以這裏c根本不涉及。

在其他情況下x是的c一個組件的引用,所以當你修改組件,你看到的變化反映在c

1

現在我想更新的第一行中的所有元素0即我想設置所有的元素在c[0]爲0。所以,我試過如下:

>>> for x in c[0]: x = 0 

>>> c 
[[None, None, None], [None, None, None], [None, None, None], [None, None, None]] 
>>> 

當你CA ñ看,元素沒有更新。

這是因爲x被賦予每個None值的C [0]反過來,所以你實際上是說:

x = None 
x = 0 
x = None 
x = 0 
x = None 
x = 0 

這沒有任何用處。然而

以下工作:

>>> c[0] = [0 for x in c[0]] 
>>> 
>>> c 
[[0, 0, 0], [None, None, None], [None, None, None], [None, None, None]] 

而且我幾乎可以肯定,它會因爲我創建0的一個新的列表,並將其分配給C [0]。

這工作,因爲c[0]c的引用,或別名,第一個元素(列表)...你可以指定一個新的值到它,它會影響原來的c容器。這裏至關重要的是寫通過參考/別名的想法。我不確定python社區傾向於稱之爲什麼(我主要是C++程序員),但功能上你的c[0]引用了c中的那個元素,並允許你覆蓋它。 c[0]與上面的x不同,因爲x有效地看到了元素值(None)的不可變副本,但該值與容器c保持無關聯或無法寫回。這是因爲None是一個簡單的「標量」值。

這個想法是,變量有時會有效地引用分配給它們的值的不可變副本,以便進一步分配將與它們分離,而其他時間實際上繼續引用分配的值本身,以便進一步寫入可以修改,實際上對於幾種解釋型語言來說很常見(例如,Ruby也是這樣做的)。起初令人困惑!這些語言希望你認爲它們比C++更簡單,比C++中這些「引用」是明確的並且有它們自己的語法/符號,但實際上你很快就會被咬,並且必須學會理解差異,然後開始使用當你需要一個真正獨立的數據副本時,需要使用copy.deepcopy。他們希望看起來很直觀,但直觀的事情 - 深刻的複製價值 - 對於大容器和對象來說太昂貴了:大多數新手的程序會變得非常慢,他們不知道爲什麼。所以,現代腳本語言傾向於採用這種危險的行爲,讓你寫信給你可能認爲你已經安全地複製了一些數據的東西。經過幾個星期的使用語言,它可能都點擊....儘管最初令人困惑,但它實際上也很有用 - 必須使用原始變量名稱引用數據,所有鍵和索引將事物縮小到特定的標量值可能會很痛苦,當然這是一個必要的抽象讓功能反正在件複雜的對象/容器他們不必瞭解對象的其餘部分通過操作....

,我再接着使用for循環,並試圖將第一列(即每一行的第一個元素)更新爲0並且工作正常。

>>> for x in c: x[0] = 0 
... 
>>> c 
[[0, None, None], [0, None, None], [0, None, None], [0, None, None]] 

此操作,因爲x結合作爲參考/別名依次在每個列表中,並作爲列表是「複雜」的對象的參考/別名允許寫通能力上述予。

我明白,這對循環更新是從前面的for循環更新不同的,因爲第一個試圖遍歷一個元素,而這一次遍歷列表,只是訪問每個列表的第一個元素。

+0

謝謝託尼。這確實有道理。我認爲你說錯了,「x剛剛收到每個元素值(無)的副本」。我不認爲x收到了「複製」。因爲我相信x只是一個創建的新名稱,它指向相同的None值。使用賦值運算符更新x會將x指向新分配的值。但是,如果None不是像列表那樣的可變對象,則x.append('foo')會更新x指向的原始列表。 hcwhsa在他的回答中談到了這一點。 – keithxm23

+0

@ keithxm23:哦,是的,這是一個偉大的觀點 - 這是一個幕後優化python爲不可變標量值執行的操作,因爲運行時類型信息等更詳細以保留在'x'內而不是簡單地創建爲某處的單例實例,並從'x'引用。但是,從用戶的角度來看,這一切都是透明的 - 如果「感覺」像'x'包含'None','3'或其他什麼。當你在'x'中寫入一個新值時,它會默默地忘記'None',而不是將'None'的值從'c'連接起來,從而明顯地更新'x'和'c'。 –

+0

@ keithxm23試圖在上面做一些更正以允許這樣做 - 不確定它是否始終如一 - 當我有更多時間時會在稍後閱讀/編輯。乾杯。 –