La raison est subtile.
Pour bien comprendre, il faut partir des notions fondamentales et remonter progressivement jusqu’à l’observation.
Accrochez vos ceintures.
FONCTION f()
Dans la fonction f() suivante, A n’est qu’utilisée pour fournir une valeur dans un calcul.
Si A existe comme variable globale définie à l’extérieur de f(), elle peut être utilisée au sein de f().
1 2 3 4 5 6
| def f():
B = 95 + A
print B
print locals(),'\n'
A = 405
f() |
Au passage, on remarque que B est crée comme une variable locale de f().
Si A n’existe pas en tant que variable globale, il y a erreur.
1 2 3 4 5
| def f():
B = 95 + A
print B
print locals(),'\n'
f() |
Traceback (most recent call last):
File "E:\Python\Essais Python\zzzz TTTT.py", line 6, in <module>
f()
File "E:\Python\Essais Python\zzzz TTTT.py", line 3, in f
B = 95 + A
NameError: global name 'A' is not defined
Pour le moment, rien de sidérant.
FONCTION g()
Dans la fonction g() suivante , A subit cette fois une assignation.
Cette assignation fait de A une variable locale qui rend sans utilité la valeur de la variable extérieure A.
1 2 3 4 5 6 7
| def g():
A = 105
B = 95 + A
print B
print locals(),'\n'
A = 405
g() |
L’assignation A = 105 consiste en deux choses:
- création d’un objet integer 105 dans la mémoire vive de l'ordinateur
- binding (attachement, association) du nom A à l’objet integer 105 créé. De façon imagée, on parle de collage de l’étiquette A sur l’objet.
Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects
http://docs.python.org/reference/simple_stmts.html
Concrètement, ce binding est réalisé par l’ajout de A à l’espace de noms de la fonction, c'est à dire par inscription du nom A dans le dictionnaire qui implémente l’espace de noms de la fonction:
A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries, but that’s normally not noticeable in any way (except for performance), and it may change in the future.
http://www.python.org/doc/2.6.2/tuto...ut-terminology
Une variable est donc dite “bound“ quand elle est présente dans un dictionnaire-espace de noms. Mais quel espace de noms ? Espace de noms relatif à quoi ?
Il faut chercher dans les docs de quoi il s'agit. C'est un peu un jeu de pistes qui n'est pas des plus clairs.
Tout espace de noms est relatif à un bloc.
Un block , c’est
a piece of Python program text that is executed as a unit.
http://docs.python.org/reference/exe...ng-and-binding
On tire l’info qu’un espace de noms est relatif à un bloc
de ceci:
Each occurrence of a name in the program text refers to the binding of that name established in the innermost function block containing the use.
(tout début de la page)
et ceci
If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.
dans
http://www.python.org/doc/2.6.2/refe...ng-and-binding
Il est question de «name established» et non pas de «name defined» parce qu’un même nom peut se trouver “établi“ dans un bloc donné, c’est à dire utilisable dans ce bloc, sans y avoir été défini formellement. L’étendue des blocs dans lesquels un nom est utilisable à partir du bloc dans lequel il a été défini s’appelle la portée (scope). En effet:
If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name.
http://docs.python.org/reference/exe...ng-and-binding
Ceci décrit que l’utilisation d’un nom NAME qui a été défini dans un bloc fonction donné peut être faite jusqu’à l’intérieur d’un «contained block» (c’est le cas de A dans la fonction f) , À MOINS QUE le «contained block» définisse un autre attachement du nom NAME (c’est le cas de A dans la fonction g puisque ce sont les assignations qui définissent un binding, cf plus haut).
Cet autre attachement est un attachement au dictionnaire-espace de noms du «contained block» et il définit NAME comme variable locale dans le «contained block». En effet:
Each occurrence of a name in the program text refers to the binding of that name established in the innermost function block containing the use.
http://www.python.org/doc/2.6.2/refe...ng-and-binding
Bien sûr, si on veut qu’une assignation dans un «contained block» ne provoque pas un nouveau binding, on doit mettre le statement global NAME.
Pour en revenir à la fonction g,
il n’y a pas de statement global A dans le bloc g qui signifierait “toute modification de A affecte la variable globale A qui existe à l’extérieur de cette fonction“.
L’assignation A = 105 fait donc de A une variable locale au même titre que B.
La valeur de A locale est utilisée dans la foulée dans un calcul affectant B.
Mais c’est l’assignation qui prime sur l’utilisation pour décréter que A est une variable locale.
FONCTION h()
Dans la fonction h(), la subtilité apparait sous forme d’une valse d’étiquette.
Il n’y a plus que la seule variable A, subissant d’abord une assignation, puis une ré-assignation.
1 2 3 4 5 6 7
| def h():
A = 5
A = 95 + A
print A
print locals(),'\n'
A = 405
h() |
L’assignation A = 5 définit A comme variable locale de la même façon que dans la précédente fonction.
Puis l’instruction A = 95 + A utilise la valeur de A pour un calcul dont le résultat affecte A elle même. C’est à dire que A = 95 + A n’est pas une assignation, c’est simplement une réassignation: A est réassignée à un nouvel objet integer 200 = l’étiquette A est déplacée d’un objet à une autre.
FONCTION p()
Dans la fonction p() , cela devient plus subtil.
1 2 3 4 5 6 7
| def p():
print locals()
A = 95 + A
print A
print locals(),'\n'
A = 405
p() |
ll y a une seule instruction A = 95 + A.
Comme il n’y a pas de statement global A et pas d’assignation préalable comme dans la fonction h() , la valeur de A à l'extérieur de la fonction n'a pas d'importance et d'utilité, et l'instruction A = 95 + A doit provoquer une assignation d’objet à la variable locale A, c’est à dire une création de l’étiquette A sur un objet nouvellement créé puisqu’en l’absence de valeur préalable de A, l’étiquette A n’existe pas à l’entrée dans la fonction.
Le problème qui survient, c’est que l’objet qui doit être assigné à la variable A doit être calculé par l’expression 95 + A dans laquelle A n’a pas de valeur au moment où doit être effectué le calcul.
Une erreur se produit donc.
{}
Traceback (most recent call last):
File "E:\Python\Essais Python\zzzz TTTT.py", line 5, in <module>
p()
File "E:\Python\Essais Python\zzzz TTTT.py", line 2, in p
A = 95 + A
UnboundLocalError: local variable 'A' referenced before assignment
Le message d’erreur signale:
« UnboundLocalError: local variable 'A' referenced before assignment »
L’erreur est logiquement qualifiée de UnboundLocalError puisqu’une assignation crée un binding comme on l’a vu: mais le programme ne trouve pas l’assignation qui devrait avoir précédé l’utilisation de A dans le calcul puisque le fait que la variable A doit être une variable locale l’empêche d’aller chercher en dehors de la fonction.
----------------------------------------
Il y a une couche supplémentaire de subtilité.
Reprenons les fonctions g() et h() qui ont réussi à créer A en tant que variable locale et à l’utiliser dans une expression de calcul.
Et inversons l’ordre des instructions.
FONCTION ginv()
1 2 3 4 5 6 7 8
| def ginv():
print locals()
B = 95 + A
A = 105
print B
print locals(),'\n'
A = 405
ginv() |
FONCTION hinv()
1 2 3 4 5 6 7 8
| def hinv():
print locals()
A = 95 + A
A = 5
print A
print locals(),'\n'
A = 405
hinv() |
Pour chacune, on obtient l’erreur:
UnboundLocalError: local variable 'A' referenced before assignment
- Cette erreur est aisément compréhensible pour hinv()
car la situation est identique pour hinv() à celle de la fonction p() :
l’instruction A = 95 + A doit être exécutée avant que vienne l’assignation A = 5; donc A = 95 + A représente une assignation qui provoque la catégorisation de A comme variable locale sans que l’expression 95 + A puisse être évaluée, à cause de l’absence de valeur pour A au départ.
- Pour ce qui est de ginv() , il faut faire un petit peu attention pour remarquer que l’expression 95 + A est rencontrée avant que l’assignation A = 105 soit rencontrée , et alors que B = 95 + A n’est pas une assignation concernant A.
Dans ces conditions, pourquoi y a-t-il erreur
UnboundLocalError: local variable 'A' referenced before assignment
comme si le programme cherchait à créer une variable locale ?
Il paraîtrait en effet tout à fait normal que la valeur de A en tant que variable globale soit utilisée au niveau de cette instruction, avant qu’à la ligne suivante sa valeur soit changée et qu’elle devienne variable locale puisqu’il n’y a pas de statement global A.
Mais la doc assure que ça ne se passe pas comme ça:
If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block.
http://www.python.org/doc/2.6.2/refe...ng-and-binding
Donc une variable donnée ne peut pas être locale (= se référer à l’«innermost block») et globale (= se référer à un «enclosing block» ) à la fois.
Et idée importante à retenir aussi: une opération d’attachement de nom de variable peut se produire n’importe où dans un bloc.
-------------------------------------
Les codes suivants montrent un peu plus ce qu’il se passe.
FONCTION utilise_A_ext()
1 2 3 4 5 6
| def utilise_A_ext():
print 'locals()',locals()
loc = 202 + A_ext
print 'locals()',locals()
A_ext = 123
utilise_A_ext() |
locals() {}
locals() {'loc': 325}
La variable loc n’est pas encore une variable locale à l’entrée du programme dans la fonction.
À la sortie, loc est devenue une telle variable locale.
La définition de loc en tant que variable locale n’intervient qu’au sein de la fonction au moment de l’assignation loc = 202 + A_ext, comme on l’a vu plus haut.
Ensuite, il faut bien croire qu’au moment d’exécuter cette instruction loc = 202 + A_ext, le programme fait une revue générale de toutes les variables intervenant dans le bloc fonction: cela se voit grâce à la fonction suivante.
FONCTION modifie_A_ext()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def modifie_A_ext():
print 'locals()',locals()
try:
loc = 66 + A_ext
try:
A_ext = 2000
except:
print 'je ne peux pas faire ca a vext !'
except:
print 'je ne peux pas executer cette instruction !'
print 'locals()',locals()
A_ext = 123
modifie_A_ext() |
locals() {}
je ne peux pas executer cette instruction !
locals() {}
On constate que l’erreur survient à la première instruction dans laquelle apparait A_ext c’est à dire loc = 66 + A_ext et non pas sur l’instruction suivante A_ext = 2000.
Cette idée a priori bizarre (le programme examine l’ensemble du bloc avant toute exécution précise) est heureusement confortée par la doc, bien que ce ne soit pas d'une absolue clarté:
Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.
http://www.python.org/doc/2.6.2/refe...ng-and-binding
Ceci signale qu’une instruction d’assignation qui déclenche un binding peut se trouver n’importe où dans un bloc: il n’y a pas de déclaration en tête de fonction comme dans d’autres langages.
Corrélativement, les variables locales d’un bloc de code sont déterminées par un scanning du texte entier du bloc.
-------------------------------------
On arrive enfin au bout du décorticage.
C’est la présence de l’assignation A_ext = 2000 qui détermine que A doit être liée comme variable locale à la fonction modifie_A_ext() , et c'est à cause de cette assignation postérieure qu’il y a donc un problème avant que cette instruction soit rencontrée, du fait que le programme examine préalablement l’ensemble du bloc de code, il anticipe.
La présence d’une instruction A_ext = 2000 + A_ext produit la même conséquence en compliquant simplement un peu plus la perception de l’enchaînement des causes: elle constitue à la fois l’assignation responsable de la catégorisation de A_ext en variable locale et une expression qui représente une utilisation de A_ext sans disposer de la valeur de A_ext.
Sous cette dernière forme, on retrouve l’apect de la fonction eggs()
Enfin la voilà !!
Dans la fonction eggs() :
l’instruction qui utilise la variable i est print i (au lieu d’une utilisation dans une expression de calcul) , tandis que i += 1 est bien une instruction qui à la fois utilise la valeur de [G]i [/G]et est une assignation qui doit provoquer la définition de i comme variable locale.
------------------------
Pour résumer (parce que je sens que tout le monde n’aura pas tout lu...), le problème apparaît particulièrement sybillin parce que la fonction
1 2 3
| def eggs ():
print 'eggs ',i
i += 1 |
regroupe 3 subtilités:
- i += 1 constitue une assignation qui définit un binding de i comme variable locale
- une opération de binding peut être placée n’importe où dans un bloc.
- l’examen de l’ensemble des noms de variables présents dans le bloc fonction est fait par scanning par le programme en amont de toute opération sur une variable dans la fonction
.
Partager