¿Por qué aparece UnboundLocalError en la segunda variable de la comprensión plana?

Respondí una pregunta aquí: la lista de comprensión en python2 funciona bien pero me sale un error en python3

El error de OP estaba usando las mismas variables para el rango máximo y los índices:

x = 12 y = 10 z = 12 n = 100 ret_list = [ (x,y,z) for x in range(x+1) for y in range(y+1) for z in range(z+1) if x+y+z!=n ] 

Este es solo un error de Python-3, y está relacionado con los ámbitos que se agregaron a la comprensión para evitar las variables definidas aquí “fugas”. Cambiar los nombres de las variables arregla eso.

El error es:

 UnboundLocalError: local variable 'y' referenced before assignment 

porque exterior, y global está sombreado por el scope local.

Mi pregunta es: ¿por qué recibo el error en y y no en z o x ?

EDITAR: Si elimino el bucle en x , el error se mueve a z :

 >> ret_list = [ (x,y,z) for y in range(y+1) for z in range(z+1) if x+y+z!=n ] UnboundLocalError: local variable 'z' referenced before assignment 

Si acabo de hacer un bucle:

 ret_list = [ (x,y,z) for y in range(y+1) if x+y+z!=n ] 

funciona. Entonces sospecho que la primera función de range se evalúa antes que todas las otras expresiones, lo que deja el valor de x intacto. Pero la razón exacta aún está por encontrarse. Utilizando Python 3.4.3.

Este comportamiento se describe (implícitamente) en la documentación de referencia (el énfasis es mío).

Sin embargo, aparte de la expresión iterable en la cláusula situada más a la izquierda for , la comprensión se ejecuta en un ámbito nested implícitamente separado separado. Esto garantiza que los nombres asignados a la lista de destino no se “filtran” en el ámbito de cierre.

La expresión iterable en la cláusula situada más a la izquierda se evalúa directamente en el ámbito adjunto y luego se pasa como un argumento al ámbito nested implícitamente [sic]. Las cláusulas posteriores y cualquier condición de filtro en la cláusula de la izquierda no se pueden evaluar en el ámbito de aplicación, ya que pueden depender de los valores obtenidos de la iterable de la izquierda. Por ejemplo: [x*y for x in range(10) for y in range(x, x+10)] .

Esto significa que:

 list_ = [(x, y) for x in range(x) for y in range(y)] 

equivalente a:

 def f(iter_): for x in iter_: for y in range(y): yield x, y list_ = list(f(iter(range(x)))) 

Como el nombre x en el iterable más a la izquierda se lee en el ámbito adjunto en lugar del ámbito nested, no hay conflicto de nombres entre estos dos usos de x . Lo mismo no es cierto para y , por lo que es donde ocurre el UnboundLocalError .

En cuanto a por qué sucede esto: una comprensión de la lista es más o menos azúcar sintáctica para la list() , por lo que se usará la misma ruta de código que una expresión de generador (o al menos se comportará de la misma manera) . Las expresiones del generador evalúan la expresión iterable en la cláusula situada más a la izquierda for cometer errores cuando la expresión del generador es algo más sensata. Considere el siguiente código:

 y = None # line 1 gen = (x + 1 for x in range(y + 1)) # line 2 item = next(gen) # line 3 

y es claramente el tipo incorrecto y, por lo tanto, la adición generará un TypeError . Al evaluar el range(y + 1) inmediatamente, el error de tipo se genera en la línea 2 en lugar de en la línea 3. Por lo tanto, es más fácil diagnosticar dónde y por qué ocurrió el problema. Si hubiera ocurrido en la línea 3, entonces podría asumir erróneamente que fue la statement x + 1 que causó el error.

Hay un informe de error aquí que menciona este comportamiento. Se resolvió como “no un error” por la razón de que es deseable que las comprensiones de lista y las expresiones generadoras tengan el mismo comportamiento.