Le code suivant fonctionne comme prévu dans Python 2.5 et 3.0:
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
Cependant, lorsque je décommente la ligne (B) , j'obtiens une UnboundLocalError: 'c' not assigned
ligne (A) . Les valeurs de a
et b
sont imprimées correctement. Cela m'a complètement dérouté pour deux raisons:
Pourquoi une erreur d'exécution est-elle renvoyée à la ligne (A) en raison d'une instruction ultérieure sur la ligne (B) ?
Pourquoi les variables sont
a
-ellesb
imprimées comme prévu, alors quec
génère une erreur?
La seule explication que je peux trouver est qu'une variable localec
est créée par l'affectation c+=1
, qui a priorité sur la variable "globale" c
avant même que la variable locale soit créée. Bien sûr, il n'est pas logique qu'une variable "vole" la portée avant qu'elle n'existe.
Quelqu'un pourrait-il expliquer ce comportement?
Réponses:
Python traite les variables dans les fonctions différemment selon que vous leur attribuez des valeurs de l'intérieur ou de l'extérieur de la fonction. Si une variable est affectée dans une fonction, elle est traitée par défaut comme une variable locale. Par conséquent, lorsque vous décommentez la ligne, vous essayez de référencer la variable locale
c
avant qu'aucune valeur ne lui ait été affectée.Si vous voulez que la variable
c
se réfère au globalc = 3
assigné avant la fonction, mettezcomme première ligne de la fonction.
Quant à python 3, il y a maintenant
que vous pouvez utiliser pour faire référence à l'étendue de la fonction englobante la plus proche qui a une
c
variable.la source
global
ounonlocal
pour forcer une affectation globale ou non locale)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)
etprint(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 lec
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
il le décompose en quelque chose comme
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.
la source
dict
, c'est en interne juste un tableau (locals()
remplira adict
pour revenir, mais les modifications apportées ne créent pas de nouveaulocals
). La phase d'analyse consiste à trouver chaque affectation en local et à convertir le nom en position dans ce tableau, et à utiliser cette position chaque fois que le nom est référencé. À l'entrée de la fonction, les sections locales sans argument sont initialisées dans un espace réservé etUnboundLocalError
s se produisent lorsqu'une variable est lue et que son index associé a toujours la valeur d'espace réservé.Un regard sur le démontage peut clarifier ce qui se passe:
Comme vous pouvez le voir, le bytecode pour accéder à un est
LOAD_FAST
, et b,LOAD_GLOBAL
. Cela est dû au fait que le compilateur a identifié qu'un a est affecté à l'intérieur de la fonction et l'a classé comme variable locale. Le mécanisme d'accès pour les locaux est fondamentalement différent pour les globaux - ils sont statiquement assignés un décalage dans la table des variables du cadre, ce qui signifie que la recherche est un index rapide, plutôt que la recherche de dict plus coûteuse que pour les globaux. Pour cette raison, Python lit laprint a
ligne comme «obtenir la valeur de la variable locale« a »contenue dans l'emplacement 0 et l'imprimer», et lorsqu'il détecte que cette variable n'est toujours pas initialisée, déclenche une exception.la source
Python a un comportement plutôt intéressant lorsque vous essayez la sémantique des variables globales traditionnelles. Je ne me souviens pas des détails, mais vous pouvez très bien lire la valeur d'une variable déclarée dans la portée «globale», mais si vous voulez la modifier, vous devez utiliser le
global
mot - clé. Essayez de passertest()
à ceci:En outre, la raison pour laquelle vous obtenez cette erreur est que vous pouvez également déclarer une nouvelle variable à l'intérieur de cette fonction avec le même nom qu'une variable «globale», et elle serait complètement distincte. L'interprète pense que vous essayez de créer une nouvelle variable dans cette portée
c
et de la modifier en une seule opération, ce qui n'est pas autorisé en Python car cette nouvellec
n'a pas été initialisée.la source
Le meilleur exemple qui le montre clairement est:
lors de l'appel
foo()
, cela augmente égalementUnboundLocalError
bien que nous n'atteindrons jamais la lignebar=0
, donc la variable logiquement locale ne doit jamais être créée.Le mystère réside dans " Python est un langage interprété " et la déclaration de la fonction
foo
est interprétée comme une instruction unique (c'est-à-dire une instruction composée), elle l'interprète simplement de façon stupide et crée des étendues locales et globales.bar
Est donc reconnu dans la portée locale avant l'exécution.Pour plus d'exemples comme celui-ci, lisez cet article: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Cet article fournit une description complète et des analyses de la portée Python des variables:
la source
Voici deux liens qui peuvent vous aider
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
le lien un décrit l'erreur UnboundLocalError. Le lien deux peut vous aider à réécrire votre fonction de test. Sur la base du lien deux, le problème d'origine pourrait être réécrit comme suit:
la source
Ce n'est pas une réponse directe à votre question, mais elle est étroitement liée, car c'est un autre problème causé par la relation entre l'affectation augmentée et les étendues de fonction.
Dans la plupart des cas, vous avez tendance à penser que l'affectation augmentée (
a += b
) est exactement équivalente à l'affectation simple (a = a + b
). Il est possible d'avoir des problèmes avec cela, dans un cas d'angle. Laisse-moi expliquer:La façon dont l'affectation simple de Python fonctionne signifie que si
a
est passé dans une fonction (commefunc(a)
; notez que Python est toujours passe-par-référence), alorsa = a + b
ne modifiera pas cea
qui est passé. Au lieu de cela, il modifiera simplement le pointeur local versa
.Mais si vous utilisez
a += b
, alors il est parfois implémenté comme:ou parfois (si la méthode existe) comme:
Dans le premier cas (tant qu'il
a
n'est pas déclaré global), il n'y a pas d'effets secondaires en dehors de la portée locale, car l'affectation àa
n'est qu'une mise à jour du pointeur.Dans le second cas,
a
se modifiera réellement, donc toutes les références àa
pointeront vers la version modifiée. Cela est démontré par le code suivant:L'astuce consiste donc à éviter l'affectation augmentée sur les arguments de fonction (j'essaie de ne l'utiliser que pour les variables locales / de boucle). Utilisez une affectation simple et vous serez à l'abri d'un comportement ambigu.
la source
L'interpréteur Python lira une fonction comme une unité complète. Je pense à cela comme le lisant en deux passes, une fois pour rassembler sa fermeture (les variables locales), puis encore pour la transformer en octet-code.
Comme je suis sûr que vous le saviez déjà, tout nom utilisé à gauche d'un '=' est implicitement une variable locale. Plus d'une fois, j'ai été rattrapé en changeant l'accès à une variable en + = et c'est soudain une variable différente.
Je voulais également souligner que cela n'a rien à voir avec la portée mondiale en particulier. Vous obtenez le même comportement avec les fonctions imbriquées.
la source
c+=1
assignec
, python suppose que les variables attribuées sont locales, mais dans ce cas, elles n'ont pas été déclarées localement.Utilisez les mots
global
-nonlocal
clés ou .nonlocal
ne fonctionne qu'en python 3, donc si vous utilisez python 2 et que vous ne voulez pas rendre votre variable globale, vous pouvez utiliser un objet mutable:la source
La meilleure façon d'atteindre la variable de classe est d'accéder directement par nom de classe
la source
En python, nous avons une déclaration similaire pour tous les types de variables locales, variables de classe et variables globales. lorsque vous faites référence à une variable globale à partir d'une méthode, python pense que vous faites réellement référence à une variable à partir de la méthode elle-même qui n'est pas encore définie et donc génère une erreur. Pour référencer une variable globale, nous devons utiliser globals () ['variableName'].
dans votre cas, utilisez globals () ['a], globals () [' b '] et globals () [' c '] au lieu de a, b et c respectivement.
la source
Le même problème me dérange. Utiliser
nonlocal
etglobal
peut résoudre le problème.Cependant, attention requise pour l'utilisation de
nonlocal
, cela fonctionne pour les fonctions imbriquées. Cependant, au niveau d'un module, cela ne fonctionne pas. Voir des exemples ici.la source