UnboundLocal Error tratando de utilizar una variable (supuesta para ser global) que se (re)asignado (incluso después del primer uso)

Cuando pruebe este código:

a, b, c = (1, 2, 3)

def test():
    print(a)
    print(b)
    print(c)
    c += 1
test()

Tengo un error del print(c) línea que dice:

UnboundLocalError: local variable 'c' referenced before assignment

en versiones más nuevas de Python, o

UnboundLocalError: 'c' not assigned

en algunas versiones anteriores.

Si pienso comentar c += 1, ambos printS tienen éxito.

No entiendo: ¿por qué imprimir a y b trabajo, si c ¿No? ¿Cómo fue? c += 1 causa print(c) fracasar, incluso cuando viene más tarde en el código?

Parece que la misión c += 1 crea un local variable c, que tiene precedencia sobre el mundo c. ¿Pero cómo puede un alcance "establecido" variable antes de que exista? ¿Por qué? c ¿Al parecer local aquí?


Véase también Utilizar variables globales en una función para preguntas que son simplemente sobre cómo reasignar una variable global desde dentro de una función, y ¿Es posible modificar una variable en pitón que está en un exterior (cerrar), pero no global, alcance? para reasignar de una función de cierre (closure).

See ¿Por qué no es necesaria la palabra clave 'global' para acceder a una variable global? para los casos en que se aplica la OP previstos un error pero no obtener uno, de acceder simplemente a un global sin el global palabra clave.

See ¿Cómo puede un nombre ser "unbound" en Python? ¿Qué código puede causar un `UnboundLocalError`? para los casos en que se aplica la OP previstos la variable a ser local, pero tiene un error lógico que evita la asignación en cada caso.

Pregunta hecha hace 15 años, 2 meses, 11 días - Por pixelpioneerc4b5


11 Respuestas:

  • Python trata variables en funciones de manera diferente dependiendo de si asigna valores a ellos 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 se descompone la línea, está tratando de hacer referencia a la variable local c antes de que se le haya asignado cualquier valor.

    Si quieres la variable c para referirse al mundo c = 3 asignado antes de la función, puesto

    global c
    

    como primera línea de la función.

    En cuanto al pitón 3, ahora hay

    nonlocal c
    

    que puede utilizar para referirse al alcance de función de cierre más cercano que tiene un c variable.

    Respondida el Dec 16, 2008 a las 03:15 - por bytebard30f9

    Votos positivos: 0 | Votos negativos: 0

  • Python es un poco raro en que mantiene todo en un diccionario para los diversos ámbitos. El original a,b,c están en el más alto alcance y así en ese diccionario más alto. La función tiene su propio diccionario. Cuando llegues a la print(a) y print(b) declaraciones, no hay nada por ese nombre en el diccionario, así que Python mira la lista y los encuentra en el diccionario global.

    Ahora llegamos c+=1, que es, por supuesto, equivalente a c=c+1. Cuando Python escanea esa línea, dice "aha, hay una variable llamada c, la pondré en mi diccionario de alcance local." Entonces cuando va buscando un valor para c para el c en el lado derecho de la asignación, encuentra su variable local llamada c, que todavía no tiene valor, y así tira el error.

    La declaración global c mencionado anteriormente simplemente le dice al analizador que utiliza el c del alcance global y no necesita uno nuevo.

    La razón por la que dice que hay un problema en la línea que hace es porque está buscando efectivamente los nombres antes de que trate de generar código, y por lo tanto en algún sentido no piensa que 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 los mensajes de un compilador demasiado En serio.

    Si es cualquier consuelo, pasé probablemente un día cavando y experimentando con este mismo asunto antes de encontrar algo que Guido había escrito sobre los diccionarios que Explicaban Todo.

    Actualizar, 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 parse de esta línea de código. El lexer lee el texto de origen y lo rompe en lexemas, los "compuestos más sólidos" de la gramática. Así que cuando golpea la línea

    c+=1
    

    se rompe en algo como

    SYMBOL(c) OPERATOR(+=) DIGIT(1)
    

    El parser finalmente quiere convertir esto en un árbol parse y ejecutarlo, pero ya que es una asignación, antes que lo haga, busca el nombre c en el diccionario local, no lo ve, e inserta en el diccionario, marcandolo como ininicializado. En un lenguaje completamente compilado, sólo entraría en la tabla de símbolos y esperaría al parse, pero ya que no tendrá el lujo de un segundo pase, el lexer hace un poco de trabajo extra para hacer la vida más fácil más adelante. Sólo, entonces ve el OPERATOR, ve que las reglas dicen "si usted tiene un operador += el lado izquierdo debe haber sido inicializado" y dice "whoops!"

    El punto aquí es que no ha comenzado la línea todavía. Todo esto está sucediendo tipo de preparación para el parse real, así que el contador de línea no ha avanzado a la siguiente línea. Así, cuando señala el error, todavía piensa en la línea anterior.

    Como digo, podrías 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 on or around line XXX", pero este no lo hace.

    Respondida el Dec 16, 2008 a las 03:23 - por algorithmarchitect

    Votos positivos: 0 | Votos negativos: 0

  • Echar un vistazo al desmontaje puede aclarar lo que está sucediendo:

    >>> def f():
    ...    print a
    ...    print b
    ...    a = 1
    
    >>> import dis
    >>> dis.dis(f)
    
      2           0 LOAD_FAST                0 (a)
                  3 PRINT_ITEM
                  4 PRINT_NEWLINE
    
      3           5 LOAD_GLOBAL              0 (b)
                  8 PRINT_ITEM
                  9 PRINT_NEWLINE
    
      4          10 LOAD_CONST               1 (1)
                 13 STORE_FAST               0 (a)
                 16 LOAD_CONST               0 (None)
                 19 RETURN_VALUE
    

    Como puede ver, el código para acceder a un es LOAD_FAST, y para b, LOAD_GLOBAL. Esto se debe a que el compilador ha identificado que a se asigna dentro de la función, y lo clasifica como una variable local. El mecanismo de acceso para los locales es fundamentalmente diferente para los globales - se asignan estadísticamente un offset 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 en cuanto a los globales. Debido a esto, Python está leyendo el print a line as "get the value of local variable 'a' held in slot 0, and print it", and when it detects that this variable is still uninitialised, raises an exception.

    Respondida el Dec 16, 2008 a las 03:30 - por binaryblossom

    Votos positivos: 0 | Votos negativos: 0

  • Python tiene un comportamiento bastante interesante cuando intenta semántica variable global tradicional. No recuerdo los detalles, pero se puede leer el valor de una variable declarada en el ámbito "global" simplemente bien, pero si desea modificarlo, tiene que utilizar el global palabra clave. Intenta cambiar test() a esto:

    def test():
        global c
        print(a)
        print(b)
        print(c)    # (A)
        c+=1        # (B)
    

    Además, la razón por la que estás recibiendo este error es porque también puedes declarar una nueva variable dentro de esa función con el mismo nombre que una 'global', y sería completamente separado. El intérprete piensa que estás tratando de hacer una nueva variable en este ámbito llamado c y modificarlo todo en una operación, que no está permitido en Python porque esta nueva c No fue inicializado.

    Respondida el Dec 16, 2008 a las 03:36 - por binaryblossom

    Votos positivos: 0 | Votos negativos: 0

  • El mejor ejemplo que lo deja claro es:

    bar = 42
    def foo():
        print bar
        if False:
            bar = 0
    

    cuando llamas foo() , esto también aumentos UnboundLocalError aunque nunca llegaremos a la línea bar=0, por lo que lógicamente la variable local nunca debe ser creada.

    El misterio está en "Python es un idioma interpretado" y la declaración de la función foo se interpreta como una sola declaración (es decir, una declaración compuesta), sólo la interpreta tontamente y crea ámbitos locales y globales. Así que... bar es reconocido en el ámbito local antes de la ejecución.

    Para más ejemplos como este Lea este post: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

    Este post proporciona una descripción completa y análisis de la configuración de Python de variables:

    Respondida el Dec 16, 2008 a las 03:46 - por algorithmadept

    Votos positivos: 0 | Votos negativos: 0

  • 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

    enlace uno describe el error UnboundLocalError. Enlace dos puede ayudar con la reescritura de su función de prueba. Basado en el enlace dos, el problema original podría ser reescrito como:

    >>> a, b, c = (1, 2, 3)
    >>> print (a, b, c)
    (1, 2, 3)
    >>> def test (a, b, c):
    ...     print (a)
    ...     print (b)
    ...     print (c)
    ...     c += 1
    ...     return a, b, c
    ...
    >>> a, b, c = test (a, b, c)
    1
    2
    3
    >>> print (a, b ,c)
    (1, 2, 4)
    

    Respondida el Dec 16, 2008 a las 03:52 - por syntaxsenseie7e4

    Votos positivos: 0 | Votos negativos: 0

  • Resumen

    Python decide el alcance de la variable antes del tiempo. A menos que estén explícitamente anulados usando el global o nonlocal (en 3,x) palabras clave, las variables serán reconocidas como local basado en existencia de cualquier operación que cambio de la unión de un nombre. Eso incluye asignaciones ordinarias, asignaciones aumentadas como +=, varias formas menos obvias de asignación for construir, anidar funciones y clases, import declaraciones...) así como UNAvinculante (utilización del). La ejecución real de dicho código es irrelevante.

    Esto también se explica en la documentación.

    Debate

    Contrario a la creencia popular, El pitón no es un lenguaje "interpretado" en cualquier sentido significativo. (Esos son muy raros ahora). La implementación de referencia de Python compila código Python de la misma manera que Java o C#: se traduce en opcodes ("bytecode") para un máquina virtual, que es entonces emulado. Otras implementaciones también deben compilar el código - para que SyntaxErrors se puede detectar sin ejecutar realmente el código, y con el fin de implementar la porción "servicios de compilación" de la biblioteca estándar.

    Cómo Python determina el alcance variable

    Durante la compilación (ya sea sobre la aplicación de referencia o no), Python sigue reglas simples para decisiones sobre el alcance variable en una función:

    • Si la función contiene un global o nonlocal declaración para un nombre, ese nombre se trata como referencia al alcance global o al primer ámbito de aplicación que contiene el nombre, respectivamente.

    • De lo contrario, si contiene alguno sintaxis para cambiar la unión (ya sea la asignación o eliminación) del nombre, incluso si el código no cambiaría realmente la unión a tiempo de ejecución, el nombre es local.

    • De lo contrario, se refiere a o bien al primer ámbito de cierre que contiene el nombre, o al alcance global de otro modo.

    Importantemente, el alcance se resuelve en tiempo de compilación. El código generado indicará directamente dónde buscar. En CPython 3.8, por ejemplo, hay opcodes separados LOAD_CONST (constantes conocidos en el tiempo de compilación) LOAD_FAST (locales), LOAD_DEREF (implement nonlocal búsqueda mirando en un cierre, que se implementa como un tuple de objetos "celulares", LOAD_CLOSURE (mirar una variable local en el objeto de cierre que fue creado para una función anida), y LOAD_GLOBAL (mira algo en el espacio de nombres global o en el espacio de nombres incorporado).

    No hay valor "default" para estos nombres. Si no han sido asignados antes de que sean buscados, NameError ocurre. Específicamente, para las búsquedas locales, UnboundLocalError ocurre; este es un subtipo NameError.

    Casos especiales (y no especiales)

    Hay algunas consideraciones importantes aquí, teniendo en cuenta que la regla de la sintaxis se aplica a tiempo de compilación, con no análisis estático:

    • It no importa si la variable global es una función integrada, etc., en lugar de un global creado explícitamente:
      def x():
          int = int('1') # `int` is local!
      
      (Por supuesto, es una mala idea sombra de nombres incorporados como este de todos modos, y global no puede ayudar - como usar el mismo código fuera de una función seguirá causando problemas)
    • It no importa si el código nunca podría ser alcanzado:
      y = 1
      def x():
          return y # local!
          if False:
              y = 0
      
    • It no importa si la asignación se optimizaría en una modificación en el lugar (por ejemplo, la extensión de una lista) - conceptualmente, el valor todavía se asigna, y esto se refleja en el bytecode en la implementación de referencia como una reasignación inútil del nombre al mismo objeto:
      y = []
      def x():
          y += [1] # local, even though it would modify `y` in-place with `global`
      
    • However, it ¿Sí? materia si hacemos una asignación indexada / piojos en su lugar. (Esto se transforma en un opcode diferente en tiempo de compilación, que a su vez llamará __setitem__)
      y = [0]
      def x():
          print(y) # global now! No error occurs.
          y[0] = 1
      
    • Hay otras formas de asignación, por ejemplo. for bucles y imports:
      import sys
      
      y = 1
      def x():
          return y # local!
          for y in []:
              pass
      
      def z():
          print(sys.path) # `sys` is local!
          import sys
      
    • Otra forma común de causar problemas con import está tratando de reutilizar el nombre del módulo como una variable local, así:
      import random
      
      def x():
          random = random.choice(['heads', 'tails'])
      
      Otra vez, import es la asignación, por lo que hay una variable global random. Pero esta variable global es no especial; puede ser tan fácilmente sombra por el local random.
    • La eliminación también está cambiando el nombre vinculante, por ejemplo:
      y = 1
      def x():
          return y # local!
          del y
      

    Se alienta al lector interesado a que, utilizando la aplicación de referencia, inspeccione cada uno de estos ejemplos utilizando el dis módulo de biblioteca estándar.

    Alcances de cierre y los nonlocal palabra clave (en 3.x)

    El problema funciona de la misma manera, mutatis mutandis, para ambos global y nonlocal Palabras clave. (Python 2.x no tiene nonlocal) De cualquier manera, la palabra clave es necesaria para asignar a la variable del alcance exterior, pero es no necesario Míralo., ni a mutate el objeto de búsqueda. (De nuevo: += en una lista muta la lista, pero entonces también reasignaciones el nombre a la misma lista.)

    Nota especial sobre mundial y edificaciones

    Como se ha visto anteriormente, Python no trata a ningún nombre como "en alcance incorporado". En su lugar, los edredones son un retroceso utilizado por las revisiones globales delscopio. Asignar a estas variables sólo actualizará el alcance global, no el alcance integrado. However, in the reference implementation, the builtin scope puede ser modificado: está representado por una variable en el espacio de nombres global llamado __builtins__, que contiene un objeto de módulo (las edificaciones se implementan en C, pero se ponen a disposición como un módulo de biblioteca estándar llamado builtins, que es preimportado y asignado a ese nombre global). Curiosamente, a diferencia de muchos otros objetos incorporados, este objeto módulo puede tener sus atributos modificados y deld. (Todo esto es, a mi entender, que se supone que se considera un detalle de implementación poco fiable; pero ha funcionado de esta manera durante bastante tiempo ahora).

    Respondida el Dec 16, 2008 a las 03:58 - por algoarchitect

    Votos positivos: 0 | Votos negativos: 0

  • El intérprete de Python leerá una función como unidad completa. Pienso en ello como leerlo en dos pases, una vez para recoger su cierre (las variables locales), y luego otra vez para convertirlo en código byte.

    Como estoy seguro de que ya estaba consciente, cualquier nombre usado en la izquierda de un '=' es implícitamente una variable local. Más de una vez me han pillado cambiando un acceso variable a un += y de repente es una variable diferente.

    También quería señalar que no es realmente nada que ver con el alcance global específicamente. Tienes el mismo comportamiento con funciones anidadas.

    Respondida el Dec 16, 2008 a las 04:06 - por syntaxsavvy

    Votos positivos: 0 | Votos negativos: 0

  • c+=1 cesiones c, pitón supone que las variables asignadas son locales, pero en este caso no se ha declarado localmente.

    O utilizar el global o nonlocal Palabras clave.

    nonlocal funciona sólo en python 3, por lo que si está usando pithon 2 y no quiere hacer su variable global, puede utilizar un objeto mutable:

    my_variables = { # a mutable object
        'c': 3
    }
    
    def test():
        my_variables['c'] +=1
    
    test()
    

    Respondida el Dec 16, 2008 a las 04:16 - por quantumquill

    Votos positivos: 0 | Votos negativos: 0

  • El error que estás experimentando se debe a que Python trata la variable c dentro de la función test() como una variable local debido a que se le intenta reasignar un valor con c += 1. Cuando intenta imprimir c antes de esta reasignación, Python se confunde ya que no ha visto ninguna declaración de la variable c como local.

    Una manera de solucionar este problema es indicando a Python que quieres usar la variable c global dentro de la función test(), para ello puedes utilizar la palabra clave global de la siguiente manera:

    python
    a, b, c = (1, 2, 3)
    
    def test():
        global c
        print(a)
        print(b)
        print(c)
        c += 1
    test()
    

    Al incluir global c dentro de la función test(), le estás indicando a Python que la variable c que se encuentra dentro de la función es la misma variable global c que se definió fuera de la función. De esta manera, el código debería funcionar correctamente y evitar el error de UnboundLocalError.

    Respondida el Dec 17, 2008 a las 03:13 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • El error UnboundLocalError ocurre cuando intentas utilizar una variable que no ha sido definida en el ámbito actual. En tu caso, la variable c se define como global fuera de la función test(), pero cuando asignas un nuevo valor a c dentro de la función (es decir, c += 1), creas una variable local c que en kenyataana sobre la variable global.

    Esto se debe a que, en Python, las asignaciones crean nuevas variables en el ámbito actual. Entonces, cuando escribes c += 1 dentro de test(), estás creando una nueva variable local c que tiene precedencia sobre la variable global c. Como resultado, cuando intentas imprimir c después de la asignación, Python busca la variable local c y no la encuentra, lo que provoca el error UnboundLocalError.

    Para solucionar este problema, puedes utilizar la palabra clave global dentro de la función test() para indicar que deseas utilizar la variable global c en lugar de crear una nueva variable local:

    python
    def test():
        global c
        print(a)
        print(b)
        print(c)
        c += 1
    

    Al utilizar la palabra clave global, le estás diciendo a Python que quieres acceder a la variable global c en lugar de crear una nueva variable local.

    Además, puedes evitar este problema utilizando la declaración nonlocal dentro de una función anidada para acceder a una variable definida en el ámbito de la función externa. Esto te permite modificar la variable definida en el ámbito externo sin crear una nueva variable local.

    python
    def outer_function():
        c = 3
    
        def inner_function():
            nonlocal c
            c += 1
    
        inner_function()
    
        print(c)  

    Imprime 4

    Respondida el Dec 17, 2008 a las 03:16 - por Gemini

    Votos positivos: 0 | Votos negativos: 0