2015-08-22 6 views
22

(non ti preoccupare, questo non è un altro domanda circa il disimballaggio tuple.)Python Più istruzioni di assegnazione in una linea

In pitone, una dichiarazione come foo = bar = baz = 5 assegna le variabili foo, bar e baz a 5. assegna queste variabili da sinistra a destra, come si può dimostrato da esempi più maliziose come

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

Ma gli stati che python language reference istruzioni di assegnamento hanno la forma

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

e su incarico viene valutato prima lo expression_list e quindi avviene l'assegnazione.

Quindi, come può essere valida la linea foo = bar = 5, dato che bar = 5 non è un expression_list? In che modo questi compiti multipli su una riga vengono analizzati e valutati? Sto leggendo il riferimento al linguaggio sbagliato?

+5

Si noti '' 'in' (target_list "=") + ', che significa una o più copie. In 'foo = bar = 5', ci sono due produzioni' (target_list "=") ', e la parte' expression_list' è semplicemente '5'. –

+0

Aha! Questo è quello che mi mancava. Se fai questa risposta, posso accettarla. Grazie! – mwcvitkovic

risposta

11

Tutto il merito va a @MarkDickinson, che ha risposto presente in un commento:

Avviso il + in (target_list "=")+, il che significa una o più copie. In foo = bar = 5, ci sono due (target_list "=") produzioni, e la parte expression_list a soli 5

Tutti target_list produzioni (vale a dire le cose che assomigliano foo =) in un'istruzione di assegnazione vengono assegnati, da sinistra a destra, al expression_list sulla a destra della dichiarazione, dopo che il expression_list viene valutato.

E, naturalmente, la solita sintassi assegnazione 'tuple-spacchettamento' opera all'interno questa sintassi, che ti permette di fare cose come

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

Mark Dickinson ha spiegato la sintassi di ciò che sta accadendo, ma gli strani esempi che riguardano lo foo mostrano che la semantica può essere contro-intuitiva.

In C, = è un operatore destro associativo che restituisce come valore RHS dell'assegnazione in modo che quando si scrive x = y = 5, y=5 viene prima valutato (assegnando 5 a y nel processo) e questo valore (5) è quindi assegnato a x.

Prima di leggere questa domanda, pensavo ingenuamente che la stessa cosa succedesse in Python. Ma, in Python =non è un'espressione (ad esempio, 2 + (x = 5) è un errore di sintassi). Quindi Python deve ottenere più incarichi in un altro modo.

Siamo in grado di smontare piuttosto che indovinare:

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

Vedi this per una descrizione delle istruzioni bytecode.

La prima istruzione spinge 5 in pila.

La seconda istruzione duplica - così ora cima alla pila ha due 5s

STORE_NAME(name) "name = Implementa TOS" secondo la documentazione codice byte

Così STORE_Name(x) implementa x = 5 (il 5 su in cima alla pila), facendo fuoriuscire 5 fuori dallo stack, dopo di che STORE_Name(y) implementa y = 5 con gli altri 5 in pila.

Il resto del bytecode non è direttamente pertinente qui.

Nel caso di foo = foo[0] = [0], il codice byte è più complicato a causa delle liste ma ha una struttura sostanzialmente simile. L'osservazione chiave è che quando l'elenco [0] viene creato e inserito nello stack, l'istruzione DUP_TOP non inserisce un'altra copia di [0] nello stack, ma inserisce un altro riferimento nell'elenco.In altre parole, in quel momento i primi due elementi dello stack sono alias per lo stesso elenco. Questo può essere visto più chiaramente nel caso un po 'più semplice:

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

Quando foo = foo[0] = [0] viene eseguito, la lista [0] viene prima assegnato a foo e poi un alias della stessa lista viene assegnato foo[0]. Questo è il motivo per cui risulta che foo è un riferimento circolare.

+0

All'inizio ho pensato la stessa cosa anche per l'associatività di destra, ma non penso che sia ciò che sta facendo Python. Se lo fosse, 'foo [0] = foo = [0]' sarebbe un'istruzione python valida, ma non lo è. Piuttosto, 'foo = foo [0] = [0]' è un'istruzione valida - una che è equivalente a 'foo = [0]; pippo [0] = pippo'. Quindi, per usare il tuo esempio, 'x = y = z = 5' viene valutato come strano-sinistra-associativo come' x = 5; y = 5; z = 5'. La trama si addensa ... – mwcvitkovic

+0

Idee interessanti! @ Mark Dickinson ha sottolineato la mia lettura errata del riferimento al linguaggio sopra, però. Ora l'intera cosa ha senso, insieme al fatto che puoi fare cose come 'foo, boo = foo [0], boo [0] = [0], [0]' – mwcvitkovic

+0

@cvitkovm Ho capito cosa stava succedendo con i casi 'foo'. Il riferimento circolare è impostato a causa di come le assegnazioni che coinvolgono le liste copiano i riferimenti piuttosto che le liste stesse. Grazie per aver posto una domanda così interessante. –

3

bar = 5 non è un'espressione. Il compito multiplo è una dichiarazione separata dalla dichiarazione di assegnazione; l'espressione è tutto a destra di destra =.

Un buon modo per pensarci è che il più importante è il più giusto =; tutto a destra di esso avviene da sinistra a destra, e tutto a sinistra di esso avviene anche da sinistra a destra.

+0

Beh, sono assolutamente d'accordo che è come viene valutato il codice, ma sto già bene su cosa fa il codice.Quello che sto cercando di capire è come Python sta analizzando le dichiarazioni come 'foo = bar = 5' quando sembra che la sintassi sia in conflitto con quanto specificato nel riferimento al linguaggio. Potresti fornire un riferimento per dove hai trovato che "Il compito multiplo è una dichiarazione separata dalla dichiarazione di assegnazione ..."? Sono riuscito a trovare solo "Assignment Statements" nella lingua di riferimento. – mwcvitkovic

+0

Potrebbe funzionare per verificare esattamente cosa fa l'implementazione cpython. –

+0

@cvitkovm Lo so, perché ho contribuito al progetto [astor] (https://github.com/berkerpeksag/astor/) che un compito è memorizzato come singolo nodo AST, ma possibilmente con [più destinazioni sul sinistra] (https://github.com/berkerpeksag/astor/blob/master/astor/code_gen.py#L238). Se guardi quel link, vedrai che ricostituire la fonte richiede la stampa di un segno di uguale per ogni obiettivo. –