Python est un peu bizarre en ce qu'il garde tout dans un dictionnaire pour les différentes étendues. Les originaux a, b, c sont dans la portée la plus élevée et donc dans ce dictionnaire le plus élevé. La fonction possède son propre dictionnaire. Lorsque vous atteignez les instructions print(a)
et print(b)
, il n'y a rien de ce nom dans le dictionnaire, donc Python recherche la liste et les trouve dans le dictionnaire global.
Nous arrivons maintenant à c+=1
, ce qui est, bien sûr, équivalent à c=c+1
. Lorsque Python scanne cette ligne, il dit "aha, il y a une variable nommée c, je vais la mettre dans mon dictionnaire de portée locale." Puis quand il va chercher une valeur pour c pour le c sur le côté droit de l'affectation, il trouve sa variable locale nommée c , qui n'a pas encore de valeur, et lance donc l'erreur.
L'instruction global c
mentionnée ci-dessus indique simplement à l'analyseur qu'il utilise le c
de la portée globale et n'a donc pas besoin d'un nouveau.
La raison pour laquelle il dit qu'il y a un problème sur la ligne qu'il fait, c'est parce qu'il recherche effectivement les noms avant d'essayer de générer du code, et donc, dans un certain sens, ne pense pas qu'il fait vraiment encore cette ligne. Je dirais que c'est un bug d'utilisation, mais c'est généralement une bonne pratique d'apprendre simplement à ne pas prendre trop au sérieux les messages d'un compilateur .
Si cela vous rassure, j'ai probablement passé une journée à creuser et à expérimenter ce même problème avant de trouver quelque chose que Guido avait écrit sur les dictionnaires qui expliquaient tout.
Mettre à jour, voir les commentaires:
Il ne scanne pas le code deux fois, mais il scanne le code en deux phases, lexing et analyse.
Considérez comment l'analyse de cette ligne de code fonctionne. Le lexer lit le texte source et le décompose en lexèmes, les "plus petits composants" de la grammaire. Alors quand ça arrive
c+=1
il le décompose en quelque chose comme
SYMBOL(c) OPERATOR(+=) DIGIT(1)
L'analyseur souhaite finalement en faire un arbre d'analyse et l'exécuter, mais comme il s'agit d'une affectation, avant de le faire, il recherche le nom c dans le dictionnaire local, ne le voit pas et l'insère dans le dictionnaire, marquant comme non initialisé. Dans un langage entièrement compilé, il irait simplement dans la table des symboles et attendrait l'analyse, mais comme il N'AURA PAS le luxe d'un deuxième passage, le lexer fait un petit travail supplémentaire pour vous faciliter la vie plus tard. Seulement, alors il voit l'OPÉRATEUR, voit que les règles disent "si vous avez un opérateur + = le côté gauche doit avoir été initialisé" et dit "whoops!"
Le point ici est qu'il n'a pas encore vraiment commencé l'analyse de la ligne . Tout cela se passe en quelque sorte préparatoire à l'analyse réelle, donc le compteur de lignes n'a pas avancé à la ligne suivante. Ainsi, quand il signale l'erreur, il pense toujours que c'est sur la ligne précédente.
Comme je l'ai dit, vous pourriez dire que c'est un bug d'utilisation, mais c'est en fait une chose assez courante. Certains compilateurs sont plus honnêtes à ce sujet et disent "erreur sur ou autour de la ligne XXX", mais celui-ci ne le fait pas.