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)
y print(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 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". 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 el c
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
c+=1
lo divide en algo como
SYMBOL(c) OPERATOR(+=) DIGIT(1)
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.