¿Cuáles son las reglas de scope de comprensión de lista dentro de una clase de Python?

En el siguiente código, la asignación de mc funciona bien en Python 2 y 3.

La asignación de cc , que utiliza la misma comprensión de lista dentro de una clase, funciona en Python 2, pero falla con Python 3.

¿Qué explica este comportamiento?

 ml1 = "abc".split() ml2 = "1 2 3".split() mc = [ i1 + i2 for i1 in ml1 for i2 in ml2 ] class Foo(object): cl1 = ml1 cl2 = ml2 cc1 = [ i1 for i1 in cl1 ] cc2 = [ i2 for i2 in cl2 ] cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ] print("mc = ", mc) foo = Foo() print("cc = ", foo.cc) 

Entiendo esto:

 (default-3.5) snafu$ python2 /tmp/z.py ('mc = ', ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']) ('cc = ', ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']) (default-3.5) snafu$ python3 /tmp/z.py Traceback (most recent call last): File "/tmp/z.py", line 5, in  class Foo(object): File "/tmp/z.py", line 11, in Foo cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ] File "/tmp/z.py", line 11, in  cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ] NameError: name 'cl2' is not defined 

¿Por qué la variable de clase cl2 no está definida? Tenga en cuenta que la asignación de cc2 funciona bien, al igual que cc1 . El intercambio de cl1 y cl2 en la comprensión muestra que el segundo bucle es el que activa la excepción, no cl2 per se.)

Versiones:

 (default-3.5) snafu$ python2 --version Python 2.7.11+ (default-3.5) snafu$ python3 --version Python 3.5.1+ 

En Python 3, las comprensiones de lista tienen su propio scope, que sigue las mismas reglas que el scope de una función. ¿Sabes cómo los métodos de una clase no se ven automáticamente dentro del scope de la clase para la búsqueda de variables?

 class Example: var = 1 def this_fails(self): print(var) Example().this_fails() # NameError 

Lo mismo se aplica a cualquier scope de función nested dentro de un scope de clase, incluido el scope de la comprensión de la lista . La búsqueda de cl2 dentro de la lista de comprensión pasa por alto el scope de la clase y va directamente a los globales. Funciona efectivamente así:

 class Foo(object): ... def make_cc(outer_iterable): result = [] for i1 in outer_iterable: for i2 in cl2: # This fails result.append(i1 + i2) return result cc = make_cc(cl1) # cl1 is evaluated outside the comprehension scope, for reasons 

Tenga en cuenta que la búsqueda de cl1 funciona bien, porque eso sucede dentro del scope de la clase, fuera de la comprensión, a pesar de estar sintácticamente nested dentro de la comprensión. Tomaron esa decisión cuando Python introdujo genexps, ya que detecta algunos errores comunes de genexp anteriormente. También es la razón por la que cc2 comprensiones de listas cc1 y cc2 ; su único uso de las variables de nivel de clase está en su exterior (solo) for iterable.

Usar comprensiones y expresiones generadoras dentro de una statement de clase es un desastre. No debería ser, pero lo es. Manténgase en los bucles regulares o ejecute las comprensiones fuera de la statement de la clase para que la semántica sea más obvia.