262 votes

Erreur de portée de variable Python

J'ai été à la programmation depuis de nombreuses années, et a récemment commencé à apprendre le Python. Le code suivant fonctionne comme prévu dans python 2.5 et 3.0 (sur OS X, si c'est important):

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

Cependant, quand j'ai dé-commenter la ligne (B), je reçois un UnboundLocalError: 'c' non assigné à la ligne (A). Les valeurs de a et b sont imprimées correctement. Cela m'a complètement dérouté pour deux raisons:

  1. Pourquoi est-il une erreur d'exécution jeté à la ligne (A) en raison d'une déclaration postérieure à la ligne (B)?

  2. Pourquoi sont des variables a et b imprimé comme prévu, alors que c génère une erreur?

La seule explication que je peux trouver, c'est que un local variable c est créé par la cession c+=1, qui prend la priorité sur le "global" de la variable c avant même la variable locale est créée. Bien sûr, il ne fait pas de sens pour une variable de "voler" portée avant qu'il existe.

Quelqu'un pourrait-il m'expliquer ce comportement?

273voto

recursive Points 34729

Python traite les variables dans les fonctions différemment selon que vous assigner des valeurs à partir de la fonction ou pas. Si vous attribuez une valeur à une variable, il est considéré par défaut comme une variable locale. Par conséquent, lorsque vous décommentez la ligne, que vous tentez de faire référence à une variable locale avant tout de la valeur a été attribuée à lui.

Si vous voulez la variable c à vous reporter à la c global mis

global c

comme la première ligne de la fonction.

Comme de python 3, il est maintenant

nonlocal c

que vous pouvez utiliser pour faire référence à la plus proche englobante (pas nécessairement global).

87voto

Charlie Martin Points 62306

bon, voici l'affaire. Python est un peu bizarre, en ce qu'elle maintient le tout dans un dictionnaire pour les différents domaines. L'original a,b,c sont dans la partie supérieure de la portée et donc, dans ce supérieure dictionnaire. La fonction dispose de son propre dictionnaire. Lorsque vous atteignez l' print(a) et print(b) des déclarations, il n'y a rien de ce nom dans le dictionnaire, donc Python regarde la liste et les trouve dans les clobal dictionnaire.

Nous en arrivons maintenant à l' c+=1, ce qui est, bien sûr, équivalent à c=c+1. Quand Python scans de cette ligne, il est dit "ahah, il y a une variable nommée c, je vais le mettre dans mon domaine local dictionnaire." Puis quand il va à la recherche pour une valeur de c pour le c sur le côté droit de la cession, il trouve sa variable locale nommée c, qui n'a pas encore de valeur, et donc génère l'erreur.

L'énoncé global c mentionné ci-dessus indique simplement à l'analyseur qu'il utilise le c de la portée mondiale et donc n'a pas besoin d'un nouveau.

La raison pour laquelle il dit qu'il y a un problème sur la ligne, il n'est parce qu'il est effectivement à la recherche pour les noms avant d'essayer de générer le code, et donc dans un certain sens, ne pensez pas qu'il est vraiment en train de faire cette ligne encore. Je dirais que c'est une fonctionnalité d'un bug, mais c'est généralement une bonne pratique pour juste apprendre à ne pas prendre un compilateur de messages trop au sérieux.

Si c'est tout le confort, j'ai probablement passé une journée à creuser et à expérimenter avec cette même question avant que je trouve quelque chose de Guido a écrit sur les dictionnaires qui m'a Tout Expliqué.

Mise à jour, voir les commentaires:

Il n'a pas de scanner le code à deux reprises, mais il n'scanner le code en deux phases, lexing et de l'analyse.

Examiner comment l'analyse de cette cline de code fonctionne. L'analyseur lexical lit le texte source et la décompose en lexèmes, le "petit composantes de la grammaire. Alors, quand il frappe la ligne

c+=1

il rompt en quelque chose comme

SYMBOL(c) OPERATOR(+=) DIGIT(1)

L'analyseur voudrait faire cela dans un arbre d'analyse et de l'exécuter, mais puisque c'est une affectation, avant qu'il ne, il semble pour le nom de c dans le local de dictionnaire, de ne pas le voir, et l'insère dans le dictionnaire, le marquer comme non initialisée. Dans un complètement langage compilé, il suffit d'aller dans la table de symboles et d'attente pour les analyser, mais depuis, il n'aura PAS le luxe d'un second passage, le lexer fait un peu de travail supplémentaire pour rendre la vie plus facile plus tard. Seulement, alors qu'il voit l'OPÉRATEUR, voit que les règles de dire "si vous avez un opérateur += la gauche doit avoir été initialisé" et dit "oups!"

Le point ici est qu'il n'a pas vraiment commencé l'analyse de la ligne de encore. Ce est tout se passe sorte de, préparatoires à la réelle analyser, de sorte que la ligne de compteur n'a pas progressé à la ligne suivante. Ainsi, lorsqu'il signale l'erreur, il pense toujours ses sur la ligne précédente.

Comme je l'ai dit, on pourrait soutenir que c'est de la convivialité d'un bug, mais sa en fait assez commun. Certains compilateurs sont plus honnêtes à ce sujet et de dire "erreur sur ou autour de la ligne XXX", mais celui-ci ne fonctionne pas.

51voto

Brian Points 48423

Un regard sur le démontage peut clarifier ce qui se passe:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

Comme vous pouvez le voir, le pseudo-code d'accès à un est - LOAD_FAST, et pour b, LOAD_GLOBAL. C'est parce que le compilateur a identifié que l'un est affecté à l'intérieur de la fonction, et l'a désigné comme une variable locale. Le mécanisme d'accès pour les habitants est fondamentalement différente pour globals - ils sont affectées de manière statique un décalage dans le cadre des variables de la table, de la signification de recherche est un moyen rapide de l'indice, plutôt que le plus cher dict de recherche comme pour les variables globales. De ce fait, Python est la lecture de l' print a ligne comme "obtenir la valeur de la variable locale 'a' qui s'est tenu dans l'emplacement 0, et l'imprimer", et lorsqu'il détecte que cette variable est toujours non initialisée, soulève une exception.

12voto

Mongoose Points 1337

Python est plutôt intéressant problème lorsque vous essayez traditionnel variable globale de la sémantique. Je ne me souviens pas des détails, mais vous pouvez lire la valeur d'une variable déclarée dans "mondiale" de portée très bien, si vous souhaitez le modifier, vous devez utiliser le mot clé global. Essayez de changer de test ():

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1       # (B)

Aussi, la raison pour laquelle vous obtenez cette erreur est parce que vous pouvez également déclarer une nouvelle variable à l'intérieur de cette fonction avec le même nom en tant que "global", et il serait tout à fait distincte. L'interprète pense que vous essayez de faire une nouvelle variable dans ce cadre appelé " c " et le modifier en une seule opération, ce qui n'est pas autorisé en python parce que cette nouvelle 'c' n'était pas initialisé.

5voto

mcdon Points 1690

Voici deux liens qui peuvent 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

lien de l'une décrit l'erreur UnboundLocalError. Lier les deux peuvent vous aider avec la avec la ré-écriture de votre fonction de test. Basé sur le lien de deux, l'original, le problème pourrait être réécrit comme suit:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X