2010-11-16 6 views
95

Las listas de comprensión están teniendo interacciones inesperadas con el alcance. Es este el comportamiento esperado?Los nombres de la lista de Python reencuentran nombres incluso después del alcance de la comprensión. ¿Es esto correcto?

Tengo un método:

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 riesgo de lloriqueo, esto es una fuente brutal de errores. A medida que escribo el nuevo código, de vez en cuando encuentro errores muy extraños debido a una nueva vinculación, incluso ahora que sé que es un problema. Necesito hacer una regla como "preceder siempre a las variables temporales en listas de comprensión con guiones bajos", pero incluso eso no es infalible.

El hecho de que exista esta especie de bomba de tiempo aleatoria en cierto modo anula toda la agradable "facilidad de uso" de las listas de comprensión.

+5

-1: "fuente brutal de errores"? Apenas. ¿Por qué elegir un término tan argumentativo? En general, los errores más costosos son malentendidos de los requisitos y errores lógicos simples. Este tipo de error ha sido un problema estándar en muchos lenguajes de programación. ¿Por qué llamarlo 'brutal'? –

+33

Viola el principio de menor sorpresa. Tampoco se menciona en la documentación de Python sobre listas de comprensión, pero sí menciona varias veces lo fácil y conveniente que es. Esencialmente es una mina terrestre que existía fuera de mi modelo de lenguaje, y por lo tanto era imposible de prever. –

+26

+1 por "fuente brutal de errores". La palabra "brutal" está * completamente * justificada. – Nathaniel

Respuesta

131

Las listas por comprensión se escapan de la variable de control del bucle en Python 2, pero no en Python 3. Aquí está Guido van Rossum (creador de Python) explaining la historia detrás de esto:

También hicimos otro cambio en Python 3 , para mejorar la equivalencia entre las listas de comprensión y las expresiones del generador . En Python 2, la lista comprensión "fugas" el control de bucle variable en el ámbito circundante:

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

Este era un artefacto del original aplicación de listas por comprensión; fue uno de los "pequeños secretos sucios " de Python durante años.Comenzó como un compromiso intencional para hacer listas de comprensiones deslumbrantemente rápido, y mientras que no era un escollo común para los principiantes , definitivamente picaron personas de vez en cuando. Para las expresiones del generador , no podríamos hacer esto. Las expresiones de generador se implementan usando generadores , cuya ejecución requiere un marco de ejecución independiente. Por lo tanto, las expresiones del generador (especialmente si iteran sobre una secuencia corta ) fueron menos eficientes que las listas de comprensión.

Sin embargo, en Python 3, decidimos solucionar el "pequeño secreto sucio" de la lista por comprensión utilizando la misma estrategia aplicación como para expresiones generadoras. Así, en Python 3, el ejemplo anterior (después de modificación a uso de impresión (x) :-) voluntad de impresión 'antes', lo que demuestra que la 'x' en la lista comprensión temporalmente sombras pero no anula la ' x ' en el ámbito circundante.

+7

Agregaré que aunque Guido lo llama un "pequeño secreto sucio", muchos lo consideraron una característica, no un error. –

+30

También tenga en cuenta que ahora en 2.7, los conjuntos y las comprensiones de diccionario (y los generadores) tienen ámbitos privados, pero las listas de comprensión aún no lo hacen. Si bien esto tiene algún sentido en el sentido de que los primeros fueron respaldados por Python 3, realmente hace que el contraste con las comprensiones de la lista sea discordante. –

+4

Sé que esta es una pregunta increíblemente vieja, pero ¿por qué algunos consideraron que era una característica del lenguaje? ¿Hay algo a favor de este tipo de filtración variable? –

7

Sí, la asignación se produce allí, al igual que en un bucle for. No se está creando un nuevo alcance.

Este es definitivamente el comportamiento esperado: en cada ciclo, el valor está ligado al nombre que especifique. Por ejemplo,

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

Una vez que se reconoció, que parece bastante fácil de evitar: no utilice los nombres existentes para las variables dentro de comprensiones.

45

Sí, las comprensiones de lista "filtran" su variable en Python 2.x, al igual que para los bucles.

En retrospectiva, esto se reconoció como un error, y se evitó con las expresiones del generador. EDIT: Como Matt B. notes también se evitó cuando se establece y comprensión diccionario sintaxis fueron portado desde Python 3. El comportamiento

listas por comprensión tuvo que ser dejado como lo es en Python 2, pero está totalmente fijada en Python 3.

Esto significa que en todos:

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 

la x siempre es local a la expresión, mientras éstos:

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

en Python 2.x todos pierden la variable x en el ámbito circundante.

2

Curiosamente, esto no afecta el diccionario ni establece la comprensión.

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

Sin embargo, se ha solucionado en 3 como se indicó anteriormente.

+0

Esa sintaxis no funciona en absoluto en Python 2.6. ¿Estás hablando de Python 2.7? –

+0

Python 2.6 tiene comprensiones de lista solo como Python 3.0. 3.1 se agregaron el conjunto y las comprensiones del diccionario, y estos se trasladaron a 2.7. Lo siento si eso no estaba claro. Tenía la intención de señalar una limitación a otra respuesta, y las versiones a las que se aplica no son del todo claras. –

+0

Aunque me puedo imaginar argumentando que hay casos en los que usar Python 2.7 para código nuevo tiene sentido, no puedo decir lo mismo de Python 2.6 ... Incluso si 2.6 es lo que vino con su SO, está no estancado con eso ¡Considere la posibilidad de instalar virtualenv y usar 3.6 para el nuevo código! –

0

algunos solución, para el pitón 2.6, cuando este comportamiento no es deseable

# 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 
Cuestiones relacionadas