El siguiente código funciona como se esperaba en Python 2.5 y 3.0:
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
Sin embargo, cuando descomento la línea (B) , obtengo una UnboundLocalError: 'c' not assigned
línea en (A) . Los valores de a
y b
se imprimen correctamente. Esto me tiene completamente desconcertado por dos razones:
¿Por qué se produce un error de tiempo de ejecución en la línea (A) debido a una declaración posterior en la línea (B) ?
¿Por qué son las variables
a
y losb
impresos como se esperaba, mientras quec
plantea un error?
La única explicación que se me ocurre es que una variable localc
la asignación creac+=1
, que tiene prioridad sobre la variable "global" c
incluso antes de que se cree la variable local. Por supuesto, no tiene sentido que una variable "robe" el alcance antes de que exista.
¿Podría alguien explicarme este comportamiento?
Respuestas:
Python trata las variables en funciones de manera diferente dependiendo de si les asigna valores desde dentro o fuera de la función. Si se asigna una variable dentro de una función, se trata por defecto como una variable local. Por lo tanto, cuando descomenta la línea, intenta hacer referencia a la variable local
c
antes de que se le haya asignado ningún valor.Si desea que la variable se
c
refiera al globalc = 3
asignado antes de la función, coloquecomo la primera línea de la función.
En cuanto a Python 3, ahora hay
que puede usar para referirse al ámbito de función de cierre más cercano que tiene una
c
variable.fuente
global
ononlocal
para forzar una asignación global o no local)Python es un poco extraño ya que mantiene todo en un diccionario para los diversos ámbitos. Los originales a, b, c están en el ámbito superior y, por lo tanto, en ese diccionario superior. La función tiene su propio diccionario. Cuando llega a las declaraciones
print(a)
yprint(b)
, no hay nada con ese nombre en el diccionario, por lo que Python busca la lista y las encuentra en el diccionario global.Ahora llegamos a
c+=1
, que es, por supuesto, equivalente ac=c+1
. Cuando Python escanea esa línea, dice "aha, hay una variable llamada c, la pondré en mi diccionario de alcance local". Luego, cuando busca un valor para c para c en el lado derecho de la asignación, encuentra su variable local llamada c , que aún no tiene valor, y arroja el error.La declaración
global c
mencionada anteriormente simplemente le dice al analizador que usa elc
del alcance global y, por lo tanto, no necesita uno nuevo.La razón por la que dice que hay un problema en la línea es porque está buscando efectivamente los nombres antes de intentar generar código, por lo que, en cierto sentido, no cree que realmente esté haciendo esa línea todavía. Yo diría que es un error de usabilidad, pero generalmente es una buena práctica aprender a no tomar demasiado en serio los mensajes de un compilador .
Si te sirve de consuelo, pasé probablemente un día cavando y experimentando con este mismo problema antes de encontrar algo que Guido había escrito sobre los diccionarios que explicaban todo.
Actualización, ver comentarios:
No escanea el código dos veces, pero escanea el código en dos fases, lexing y parsing.
Considere cómo funciona el análisis de esta línea de código. El lexer lee el texto fuente y lo divide en lexemas, los "componentes más pequeños" de la gramática. Entonces cuando llega a la línea
lo divide en algo como
El analizador finalmente quiere convertir esto en un árbol de análisis y ejecutarlo, pero como es una tarea, antes de que lo haga, busca el nombre c en el diccionario local, no lo ve y lo inserta en el diccionario, marcando como no inicializado. En un lenguaje completamente compilado, simplemente iría a la tabla de símbolos y esperaría el análisis, pero como NO tendrá el lujo de una segunda pasada, el lexer hace un poco de trabajo extra para facilitar la vida más adelante. Solo, luego ve al OPERADOR, ve que las reglas dicen "si tienes un operador + = el lado izquierdo debe haber sido inicializado" y dice "¡vaya!"
El punto aquí es que todavía no ha comenzado realmente el análisis de la línea . Todo esto está sucediendo como una preparación para el análisis real, por lo que el contador de línea no ha avanzado a la siguiente línea. Por lo tanto, cuando señala el error, todavía piensa que está en la línea anterior.
Como digo, podría argumentar que es un error de usabilidad, pero en realidad es algo bastante común. Algunos compiladores son más honestos al respecto y dicen "error en o alrededor de la línea XXX", pero este no.
fuente
dict
, es internamente solo una matriz (locals()
se rellenará adict
para regresar, pero los cambios no crean nuevoslocals
). La fase de análisis es encontrar cada asignación a un local y convertir de nombre a posición en esa matriz, y usar esa posición siempre que se haga referencia al nombre. Al ingresar a la función, los locales sin argumentos se inicializan en un marcador de posición, yUnboundLocalError
s ocurren cuando se lee una variable y su índice asociado todavía tiene el valor del marcador de posición.Echar un vistazo al desmontaje puede aclarar lo que está sucediendo:
Como se puede ver, el código de bytes para acceder a es
LOAD_FAST
, y por b,LOAD_GLOBAL
. Esto se debe a que el compilador ha identificado que a está asignado dentro de la función y lo ha clasificado como una variable local. El mecanismo de acceso para los locales es fundamentalmente diferente para los globales: se les asigna un desplazamiento estático en la tabla de variables del marco, lo que significa que la búsqueda es un índice rápido, en lugar de la búsqueda de dict más costosa como para los globales. Debido a esto, Python está leyendo laprint a
línea como "obtener el valor de la variable local 'a' en el espacio 0 e imprimirlo", y cuando detecta que esta variable aún no se ha inicializado, genera una excepción.fuente
Python tiene un comportamiento bastante interesante cuando prueba la semántica de variable global tradicional. No recuerdo los detalles, pero puede leer el valor de una variable declarada en el alcance 'global' muy bien, pero si desea modificarlo, debe usar la
global
palabra clave. Intenta cambiartest()
a esto:Además, la razón por la que obtiene este error es porque también puede declarar una nueva variable dentro de esa función con el mismo nombre que una 'global', y estaría completamente separada. El intérprete cree que está tratando de crear una nueva variable en este ámbito llamada
c
y modificarla en una sola operación, lo que no está permitido en Python porque esta nuevac
no se inicializó.fuente
El mejor ejemplo que lo deja claro es:
cuando se llama
foo()
, esto también aumentaUnboundLocalError
aunque nunca llegaremos a la líneabar=0
, por lo que lógicamente nunca se debe crear una variable local.El misterio radica en " Python es un lenguaje interpretado " y la declaración de la función
foo
se interpreta como una sola declaración (es decir, una declaración compuesta), simplemente la interpreta tontamente y crea ámbitos locales y globales. Porbar
lo tanto, se reconoce en el ámbito local antes de la ejecución.Para obtener más ejemplos como este, lea esta publicación: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Esta publicación proporciona una descripción completa y análisis del alcance de Python de variables:
fuente
Aquí hay dos enlaces que pueden ayudar
1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference
el enlace uno describe el error UnboundLocalError. El enlace dos puede ayudarlo a reescribir su función de prueba. Basado en el enlace dos, el problema original podría reescribirse como:
fuente
Esta no es una respuesta directa a su pregunta, pero está estrechamente relacionada, ya que es otro problema causado por la relación entre la asignación aumentada y los ámbitos de función.
En la mayoría de los casos, tiende a pensar en la asignación aumentada (
a += b
) como exactamente equivalente a la asignación simple (a = a + b
). Sin embargo, es posible tener algunos problemas con esto, en un caso de esquina. Dejame explicar:La forma en que funciona la asignación simple de Python significa que si
a
se pasa a una función (comofunc(a)
; tenga en cuenta que Python siempre se pasa por referencia), entoncesa = a + b
no modificará loa
que se pasa. En su lugar, solo modificará el puntero locala
.Pero si lo usa
a += b
, a veces se implementa como:o a veces (si el método existe) como:
En el primer caso (siempre que
a
no se declare global), no hay efectos secundarios fuera del ámbito local, como la asignación aa
es solo una actualización del puntero.En el segundo caso,
a
se modificará a sí mismo, por lo que todas las referencias aa
apuntarán a la versión modificada. Esto se demuestra con el siguiente código:Entonces, el truco es evitar la asignación aumentada en los argumentos de la función (trato de usarlo solo para variables locales / de bucle). Utilice una asignación simple y estará a salvo de comportamientos ambiguos.
fuente
El intérprete de Python leerá una función como una unidad completa. Pienso en ello como leerlo en dos pasadas, una para reunir su cierre (las variables locales), luego otra vez para convertirlo en código de bytes.
Como estoy seguro de que ya sabía, cualquier nombre utilizado a la izquierda de un '=' es implícitamente una variable local. Más de una vez me ha sorprendido cambiando un acceso variable a a + = y de repente es una variable diferente.
También quería señalar que no tiene nada que ver específicamente con el alcance global. Obtiene el mismo comportamiento con funciones anidadas.
fuente
c+=1
asignac
, python supone que las variables asignadas son locales, pero en este caso no se ha declarado localmente.O usa el
global
ononlocal
palabras clave .nonlocal
funciona solo en python 3, por lo que si está usando python 2 y no desea que su variable sea global, puede usar un objeto mutable:fuente
La mejor manera de llegar a la variable de clase es acceder directamente por nombre de clase
fuente
En python tenemos una declaración similar para todo tipo de variables locales, variables de clase y variables globales. cuando refiere la variable global del método, Python piensa que en realidad está refiriendo la variable del método en sí que aún no está definida y, por lo tanto, arroja un error. Para referirnos a la variable global tenemos que usar globals () ['variableName'].
en su caso use globals () ['a], globals () [' b '] y globals () [' c '] en lugar de a, byc respectivamente.
fuente
El mismo problema me molesta. Usando
nonlocal
yglobal
puede resolver el problema.Sin embargo, la atención necesaria para el uso de
nonlocal
, funciona para funciones anidadas. Sin embargo, en un nivel de módulo, no funciona. Ver ejemplos aquí.fuente