UnboundLocalError cuando manipula variables produce un comportamiento inconsistente

En Python, el siguiente código funciona:

a = 1 b = 2 def test(): print a, b test() 

Y el siguiente código funciona:

 a = 1 b = 2 def test(): if a == 1: b = 3 print a, b test() 

Pero lo siguiente no funciona:

 a = 1 b = 2 def test(): if a == 1: a = 3 print a, b test() 

El resultado de este último bloque es un mensaje UnboundLocalError , que dice que a se hace referencia antes de la asignación.

Entiendo que puedo hacer que el último bloque funcione si agrego global a en la definición de test() , para que sepa de qué estoy hablando.

¿Por qué no recibo un error al asignar un nuevo valor a b ?

¿Estoy creando una variable b local y no me grita porque no estoy tratando de hacer referencia a ella antes de la asignación?

Pero si ese es el caso, ¿por qué puedo print a, b en el caso del primer bloque, sin tener que declarar global a, b antemano?

Déjame darte el enlace a los documentos donde se menciona claramente.

Si a una variable se le asigna un nuevo valor en cualquier lugar dentro del cuerpo de la función, se asume que es un local .

(énfasis mío)

Por lo tanto, su variable a es una variable local y no global. Esto es porque tienes una statement de asignación,

 a = 3 

En la tercera línea de tu código. Esto hace a variable local. Y al referirse a la variable local antes de la statement, se produce un error que es un UnboundLocalError .

Sin embargo, en su segundo bloque de código, no realiza ninguna de estas declaraciones de asignación y, por lo tanto, no recibe ningún error de este tipo.

Otro enlace útil es este

Se genera cuando se hace una referencia a una variable local en una función o método, pero no se ha vinculado ningún valor a esa variable.

Por lo tanto, te refieres a la variable local que creas en la siguiente línea.

Para evitar esto hay dos formas.

  • Buena manera – Pasando parámetros

    Defina su función como def test(a): y llámela como test(a)

  • Mala manera – Usando global

    Tenga una línea global a en la parte superior de su llamada de función.

¡Las reglas de scope de Python son un poco difíciles! Necesitas dominarlos para dominar el idioma. Mira esto

En el tercer bloque, el comstackdor ha marcado a variable local desde que se asigna, por lo tanto, cuando se usa en la expresión, se busca en el ámbito local. Como no existe allí, se genera una excepción.

En el segundo bloque, el comstackdor ha marcado b como una variable local pero no como a , por lo tanto, no hay excepción cuando se accede a a, ya que se buscarán los ámbitos externos.

Cuando modifica a , se convierte en una variable local. Cuando simplemente estás haciendo referencia a él, es un global. No ha definido a en el ámbito local, por lo que no puede modificarla.

Si desea modificar un global, debe llamarlo global en su ámbito local.

Eche un vistazo al bytecode para lo siguiente

 import dis a = 9 # Global def foo(): print a # Still global def bar(): a += 1 # This "a" is local dis.dis(foo) 

Salida:

  2 0 LOAD_GLOBAL 0 (a) 3 PRINT_ITEM 4 PRINT_NEWLINE 5 LOAD_CONST 0 (None) 8 RETURN_VALUE 

Para la segunda función:

 dis.dis(bar) 

Salida:

  2 0 LOAD_FAST 0 (a) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_FAST 0 (a) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE 

El primer bytecode de la función carga el global a ( LOAD_GLOBAL ) porque solo se hace referencia a él. El bytecode de la segunda función ( LOAD_FAST ) intenta cargar un a local pero uno no se ha definido.

La única razón por la que su segunda función funciona es porque a es igual a 1 . Si a fuera de 1 , la asignación local a b no se produciría y recibiría el mismo error.