2015-08-22 60 views
22

(不要擔心,這是不是拆包元組的另一個問題。)Python進行多種賦值語句在一行

在蟒蛇,就像foo = bar = baz = 5聲明分配變量富,酒吧和巴茲5。它由左到右分配這些變量,可以通過厲害的例子一樣

>>> foo[0] = foo = [0] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
NameError: name 'foo' is not defined 
>>> foo = foo[0] = [0] 
>>> foo 
[[...]] 
>>> foo[0] 
[[...]] 
>>> foo is foo[0] 
True 

證明但python language reference指出賦值語句的形式爲

(target_list "=")+ (expression_list | yield_expression) 

並首先評估expression_list,然後進行分配。

那麼foo = bar = 5行怎麼能有效,因爲bar = 5不是expression_list?這些多行分配如何在一行中得到解析和評估?我閱讀語言參考錯誤嗎?

+5

注意'(target_list「=」)+'中的'+',表示一個或多個副本。在'foo = bar = 5'中,有兩個((target_list「=」)產品,而'expression_list'部分只是'5'。 –

+0

啊哈!這就是我所錯過的。如果你讓這個答案我可以接受。謝謝! – mwcvitkovic

回答

11

一切歸功於@MarkDickinson,誰在評論回答了這個:

注意的+(target_list "=")+,這意味着一個或多個副本。在foo = bar = 5,有兩個(target_list "=")生產和expression_list部分只是5

所有target_list作品(即看起來像foo =的東西)在賦值語句得到分配,由左到右,到expression_list上在expression_list得到評估後,聲明的右端。

當然通常的「元組拆包」賦值語法的工作原理是語法中,讓你不喜歡的東西

>>> foo, boo, moo = boo[0], moo[0], foo[0] = moo[0], foo[0], boo[0] = [0], [0], [0] 
>>> foo 
[[[[...]]]] 
>>> foo[0] is boo 
True 
>>> foo[0][0] is moo 
True 
>>> foo[0][0][0] is foo 
True 
6

馬克迪金森解釋了正在發生的事情的語法,但涉及foo的奇怪示例表明,語義可能是違反直覺的。

在C中,=是右結合運算符,它返回作爲分配的RHS,以便當你寫x = y = 5y=5首先計算的值(在此過程中分配5至y)並且該值(5)是然後分配到x

在我讀到這個問題之前,我天真地認爲在Python中發生了大致相同的事情。但是,在Python =不是的一個表達式(例如,2 + (x = 5)是語法錯誤)。所以Python必須以另一種方式實現多個分配。

我們可以拆卸,而不是猜測:

>>> import dis 
>>> dis.dis('x = y = 5') 
    1   0 LOAD_CONST    0 (5) 
       3 DUP_TOP 
       4 STORE_NAME    0 (x) 
       7 STORE_NAME    1 (y) 
      10 LOAD_CONST    1 (None) 
      13 RETURN_VALUE 

爲的字節碼指令的說明,請參見this

第一條指令將5壓入堆棧。

第二指令複製它 - 所以現在堆棧頂部有兩個5S

STORE_NAME(name)「器具名稱= TOS」按字節代碼文檔

因此STORE_Name(x)上實現x = 5(5在堆棧中彈出5個堆棧,之後STORE_Name(y)執行y = 5堆棧中的其他5個堆棧。

字節碼的其餘部分在這裏並不直接相關。

foo = foo[0] = [0]的情況下,字節碼由於列表而更復雜,但具有基本相似的結構。關鍵的觀察是,一旦列表[0]被創建並被放置在堆棧上,則指令DUP_TOP不會將另一個副本[0]放置在堆棧上,而是將另一個參考放置到列表中。換句話說,在該階段堆棧的前兩個元素是同一列表的別名。這可以最清楚地看到在有些簡單的情況:

>>> x = y = [0] 
>>> x[0] = 5 
>>> y[0] 
5 

當執行foo = foo[0] = [0],列表[0]首先分配給foo,然後在同一列表的別名分配給foo[0]。這就是爲什麼它會導致foo成爲循環引用。

+0

我認爲最初也是關於右結合的東西,但我不認爲這就是python的做法。如果是這樣,'foo [0] = foo = [0]'是一個有效的python語句,但事實並非如此。相反,'foo = foo [0] = [0]'是一個有效的語句 - 等同於'foo = [0]; foo [0] = foo'。因此,要使用你的例子,「x = y = z = 5」被怪異地左聯合地評價爲「x = 5; y = 5; z = 5'。情節變厚... – mwcvitkovic

+0

有趣的想法!儘管如此,@MarkDickinson指出我誤解了上面的語言參考。現在整個事情是有道理的,以及你可以做像'foo,boo = foo [0],boo [0] = [0],[0]' – mwcvitkovic

+0

@cvitkovm這樣的事實,我知道發生了什麼事'foo'案件。循環引用是由於涉及列表的分配如何複製引用而不是列表本身而設置的。感謝您提出這樣一個有趣的問題。 –

3

bar = 5不是一個表達式。多重任務是來自任務說明的單獨聲明;該表達式就是最右邊=右側的所有內容。

想一想的好方法是,最右邊的=是主要的分隔符;它的右邊的所有內容都是從左到右發生的,左邊的所有內容都是從左到右發生的。

+0

嗯,我絕對同意這是如何評估代碼,但我已經對代碼的作用沒問題。我試圖弄清楚的是,當語法與語言引用中指定的語法衝突時,python是如何解析'foo = bar = 5'這樣的語句的。您能否提供一個參考資料,以瞭解您發現的情況:「多重任務是來自任務說明的單獨聲明...」?我只能在語言參考中找到'Assignment Statements'。 – mwcvitkovic

+0

它可能會檢查cpython實現的確切功能。 –

+0

@cvitkovm我知道,因爲我已經對[astor](https://github.com/berkerpeksag/astor/)項目做出了貢獻,即某個任務被存儲爲單個AST節點,但可能與[多個目標左](https://github.com/berkerpeksag/astor/blob/master/astor/code_gen.py#L238)。如果您查看該鏈接,您將看到重構源代碼需要爲每個目標打印一個等號。 –