2010-11-16 4 views
95

Le comprensioni delle liste hanno interazioni inaspettate con l'ambito. È questo il comportamento previsto?Nomi di riassociazione per la comprensione degli elenchi di Python anche dopo l'ambito della comprensione. È giusto?

Ho un metodo:

def leave_room(self, uid): 
    u = self.user_by_id(uid) 
    r = self.rooms[u.rid] 

    other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid] 
    other_us = [self.user_by_id(uid) for uid in other_uids] 

    r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above 

    # Interestingly, it's rebound to the last uid in the list, so the error only shows 
    # up when len > 1 

A rischio di piagnucolare, questa è una fonte di errori brutale. Mentre scrivo un nuovo codice, di tanto in tanto trovo degli errori molto strani a causa del ribellarsi - anche ora che so che è un problema. Ho bisogno di fare una regola del tipo "sempre prefigura i vari del tempo nelle list comprehensions con underscore", ma anche questo non è infallibile.

Il fatto che ci sia questo tipo di bomba a tempo casuale in attesa nega tutta la piacevole "facilità d'uso" delle list comprehensions.

+5

-1: "fonte brutale di errori"? Quasi. Perché scegliere un termine così argomentativo? Generalmente gli errori più costosi sono fraintendimenti dei requisiti e semplici errori logici. Questo tipo di errore è stato un problema standard in molti linguaggi di programmazione. Perché chiamarlo 'brutale'? –

+33

viola il principio di minima sorpresa. Inoltre non è menzionato nella documentazione di Python sulla comprensione delle liste che tuttavia menziona più volte quanto siano semplici e comode. Essenzialmente è una miniera di terra che esisteva al di fuori del mio modello linguistico, e quindi era impossibile per me prevederlo. –

+26

+1 per "fonte di errori brutali". La parola "brutale" è * interamente * giustificata. – Nathaniel

risposta

131

di lista di perdite la variabile di controllo del ciclo in Python 2, ma non in Python 3. Qui di Guido van Rossum (creatore di Python) explaining la storia dietro questo:

Abbiamo anche fatto un altro cambiamento in Python 3 , per migliorare l'equivalenza tra le espressioni di lista e le espressioni di generatore . In Python 2, l'elenco comprensione "fughe" il controllo ad anello variabile nel perimetro circostante:

x = 'before' 
a = [x for x in 1, 2, 3] 
print x # this prints '3', not 'before' 

Questo era un artefatto del implementazione originale comprehensions lista; era uno dei "piccoli sporchi segreti di Python" per anni.Cominciò come un compromesso intenzionale per rendere la comprensione della lista accecantemente veloce, e mentre non era una trappola comune per i principianti , sicuramente punse le persone di tanto in tanto. Per le espressioni del generatore non siamo riusciti a farlo. Le espressioni del generatore sono implementate utilizzando generatori, la cui esecuzione richiede un frame di esecuzione separato. Quindi, le espressioni del generatore (in particolare se iterano su una sequenza breve ) erano meno efficienti rispetto alla comprensione degli elenchi.

Tuttavia, in Python 3, abbiamo deciso di fissare il "piccolo sporco segreto" della lista comprensioni utilizzando la stessa strategia attuazione, per espressioni generatore. Quindi, in Python 3, l'esempio precedente (dopo la modifica da usare print (x) :-) sarà stampando 'prima', dimostrando che 'x' nella comprensione dell'elenco temporaneamente ombre ma non sovrascrive il ' x ' nell'ambito circostante.

+7

Aggiungerò che sebbene Guido lo chiami "sporco piccolo segreto", molti lo considerano una caratteristica, non un bug. –

+30

Si noti inoltre che ora in 2.7, le comprensibilità di set e dizionario (e generatori) hanno ambiti privati, ma le intese delle liste continuano a non esserlo. Mentre questo ha un senso nel fatto che i primi sono stati tutti portati indietro da Python 3, rende davvero il contrasto con la comprensione delle liste stridente. –

+4

So che questa è una domanda insanamente vecchia, ma per quale motivo l'ho considerata una caratteristica della lingua? C'è qualcosa a favore di questo tipo di perdite variabili? –

7

Sì, l'assegnazione si verifica lì, proprio come farebbe in un ciclo for. Non è stato creato alcun nuovo ambito.

Questo è sicuramente il comportamento previsto: in ogni ciclo, il valore è associato al nome specificato. Per esempio,

>>> x=0 
>>> a=[1,54,4,2,32,234,5234,] 
>>> [x for x in a if x>32] 
[54, 234, 5234] 
>>> x 
5234 

Una volta che è riconosciuto, sembra abbastanza facile da evitare: non utilizzare i nomi esistenti per le variabili all'interno di comprensioni.

45

Sì, la comprensione delle liste "perde" la loro variabile in Python 2.x, proprio come per i loop.

In retrospettiva, questo è stato riconosciuto come un errore ed è stato evitato con le espressioni del generatore. EDIT: Come Matt B. notes è stato anche evitato quando impostati e di comprensione dizionario sintassi sono stati backport da Python comportamento 3.

di lista doveva essere lasciata così com'è in Python 2, ma è completamente risolto in Python 3.

Ciò significa che in tutti:

list(x for x in a if x>32) 
set(x//4 for x in a if x>32)   # just another generator exp. 
dict((x, x//16) for x in a if x>32) # yet another generator exp. 
{x//4 for x in a if x>32}   # 2.7+ syntax 
{x: x//16 for x in a if x>32}  # 2.7+ syntax 

il x è sempre locale all'espressione mentre questi:

[x for x in a if x>32] 
set([x//4 for x in a if x>32])   # just another list comp. 
dict([(x, x//16) for x in a if x>32]) # yet another list comp. 

in Python 2.x tutti perdono la variabile x nell'ambito circostante.

2

È interessante notare che ciò non influisce sulle definizioni del dizionario o dell'insieme.

>>> [x for x in range(1, 10)] 
[1, 2, 3, 4, 5, 6, 7, 8, 9] 
>>> x 
9 
>>> {x for x in range(1, 5)} 
set([1, 2, 3, 4]) 
>>> x 
9 
>>> {x:x for x in range(1, 100)} 
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99} 
>>> x 
9 

Tuttavia è stato risolto in 3 come indicato in precedenza.

+0

Questa sintassi non funziona affatto in Python 2.6. Stai parlando di Python 2.7? –

+0

Python 2.6 ha solo una lista come Python 3.0. 3.1 aggiunto set e dizionario di comprensione e questi sono stati portati a 2.7. Scusa se non era chiaro. Era pensato per notare una limitazione ad un'altra risposta, e quali versioni si applica a non è del tutto semplice. –

+0

Mentre posso immaginare di argomentare che ci sono casi in cui l'uso di python 2.7 per il nuovo codice ha senso, non posso dire lo stesso per python 2.6 ... Anche se 2.6 è ciò che è venuto con il tuo sistema operativo, sei non bloccato con esso. Prendi in considerazione l'installazione di virtualenv e l'utilizzo di 3.6 per il nuovo codice! –

0

qualche soluzione alternativa, per Python 2.6, quando questo comportamento non è desiderabile

# python 
Python 2.6.6 (r266:84292, Aug 9 2016, 06:11:56) 
Type "help", "copyright", "credits" or "license" for more information. 
>>> x=0 
>>> a=list(x for x in xrange(9)) 
>>> x 
0 
>>> a=[x for x in xrange(9)] 
>>> x 
8