8 Stimmen

Python globals, locals, and UnboundLocalError Python globals, locals, und UnboundLocalError

Ich bin kürzlich auf diesen Fall von UnboundLocalError gestoßen, der seltsam erscheint:

import pprint

def main():
    if 'pprint' in globals(): print 'pprint is in globals()'
    pprint.pprint('Spam')
    from pprint import pprint
    pprint('Eggs')

if __name__ == '__main__': main()

Was folgendes produziert:

pprint is in globals()
Traceback (most recent call last):
  File "weird.py", Zeile 9, in 
    if __name__ == '__main__': main()
  File "weird.py", Zeile 5, in main
    pprint.pprint('Spam')
UnboundLocalError: lokale Variable 'pprint' vor Zuweisung referenziert

pprint ist offensichtlich in globals gebunden und wird in der folgenden Anweisung in locals gebunden sein. Kann jemand eine Erklärung dafür bieten, warum es nicht zufrieden ist, pprint hier auflösen zu können?

Bearbeiten: Dank der guten Antworten kann ich meine Frage mit relevanten Fachbegriffen klarstellen:

Zur Kompilierungszeit wird der Bezeichner pprint als lokal zum Rahmen markiert. Hat das Ausführungsmodell keinen Unterschied wo innerhalb des Rahmens der lokale Bezeichner gebunden ist? Kann es sagen, "beziehe dich auf die globale Bindung bis zu diesem Bytecode-Befehl, ab dem Zeitpunkt wurde er an eine lokale Bindung neu gebunden," oder berücksichtigt das Ausführungsmodell dies nicht?

0 Stimmen

6voto

Albert Visser Punkte 1124

Wo ist die Überraschung? Jede Variable, die global für einen Bereich ist, wird vom Compiler als lokal für diesen Bereich markiert, wenn sie innerhalb dieses Bereichs neu zugewiesen wird.

Wenn Imports anders behandelt würden, das wäre meiner Meinung nach überraschend.

Es könnte dafür sprechen, Module nicht nach den darin verwendeten Symbolen zu benennen, oder umgekehrt.

0 Stimmen

Du hast definitiv recht - es ist dasselbe Verhalten bei jeder neu zugewiesenen Variable in einem Scope. +1

5voto

JV. Punkte 2530

Nun, das war interessant genug für mich, um ein wenig zu experimentieren, und ich las durch http://docs.python.org/reference/executionmodel.html

Dann habe ich hier und da ein wenig an deinem Code herumgespielt, das ist was ich finden konnte:

code:

import pprint

def two():
    from pprint import pprint
    print globals()['pprint']
    pprint('Eggs')
    print globals()['pprint']

def main():
    if 'pprint' in globals():
        print 'pprint ist in globals()'
    global  pprint
    print globals()['pprint']
    pprint.pprint('Spam')
    from pprint import pprint
    print globals()['pprint']
    pprint('Eggs')

def three():
    print globals()['pprint']
    pprint.pprint('Spam')

if __name__ == '__main__':
    two()
    print('\n')
    three()
    print('\n')
    main()

output:

'Eggs'

'Spam'

pprint ist in globals()

'Spam'

'Eggs'

In der Methode two() from pprint import pprint überschreibt jedoch den Namen pprint in globals nicht, da das Schlüsselwort global im Bereich von two() nicht verwendet wird.

In der Methode three() wird da keine Deklaration des Namens pprint im lokalen Bereich vorhanden ist, standardmäßig auf den globalen Namen pprint

Während in main(), wird zuerst das Schlüsselwort global benutzt, sodass alle Verweise auf pprint im Bereich der Methode main() auf den globalen Namen pprint verweisen werden. Wie wir sehen können, ist dies zuerst ein Modul und wird im globalen Namensraum mit einer Methode überschrieben, wenn wir from pprint import pprint durchführen.

Auch wenn dies die Frage nicht wirklich beantwortet, so ist es trotzdem eine interessante Tatsache denke ich.

\=====================

Bearbeiten Noch eine interessante Sache.

Wenn du ein Modul hast, sagen wir:

mod1

from datetime import datetime

def foo():
    print "bar"

und eine weitere Methode sagen wir:

mod2

import datetime
from mod1 import *

if __name__ == '__main__':
    print datetime.datetime.now()

was auf den ersten Blick scheinbar korrekt ist, da du das Modul datetime in mod2 importiert hast.

Wenn du nun versuchst, mod2 als Skript auszuführen, wird ein Fehler auftreten:

Traceback (most recent call last):
  File "mod2.py", line 5, in 
    print datetime.datetime.now()
AttributeError: type object 'datetime.datetime' has no attribute 'datetime'

weil der zweite Import from mod2 import * den Namen datetime im Namespace überschrieben hat, daher ist der erste import datetime nicht mehr gültig.

Moral: Daher ist die Reihenfolge der Importe, die Art der Importe (von x import) und das Bewusstsein für Importe innerhalb importierter Module - sind wichtig.

0 Stimmen

+1 für den großartigen Link (und danke für deinen Kommentar in @Triptychs Antwort - er hat mir geholfen, die Frage zu klären).

4voto

Kenan Banks Punkte 196831

Es sieht so aus, als ob Python die Zeile from pprint import pprint sieht und pprint als lokal für main() kennzeichnet bevor er irgendwelchen Code ausführt. Da Python glaubt, dass pprint eine lokale Variable sein sollte, wirft es beim Referenzieren von pprint.pprint() bevor es mit der from..import Anweisung "zugewiesen" wird jenen Fehler.

Soviel Sinn kann ich daraus machen.

Die Moral ist natürlich, diese import Anweisungen immer an den Anfang des Geltungsbereichs zu setzen.

0 Stimmen

@cdleary: Wenn Sie innerhalb derselben Methode zunächst einen Namen aus dem globalen Bereich verwenden und dann für den lokalen Bereich, gibt es keinen impliziten Weg für den Compiler, festzustellen, dass das erste Auftreten das globale Namensraum bezieht und das zweite das lokale. Daher wird es als UnboundLocalError behandelt.

1 Stimmen

Also die Schlussfolgerung, die wir aus dieser Analyse ziehen würden, ist, dass globale Bezeichner in einem lokalen Bereich nicht früher verwendet werden können, wenn sie später im lokalen Bereich gebunden sind?

4voto

Larry Hastings Punkte 2546

Diese Frage wurde vor einigen Wochen beantwortet, aber ich denke, ich kann die Antworten ein wenig klarer machen. Erst einige Fakten.

1: In Python,

import foo

ist fast genau das gleiche wie

foo = __import__("foo", globals(), locals(), [], -1)

2: Beim Ausführen von Code in einer Funktion sucht Python, wenn es auf eine Variable trifft, die noch nicht in der Funktion definiert wurde, im globalen Scope.

3: Python hat eine Optimierung für Funktionen namens "locals". Wenn Python eine Funktion tokenisiert, verfolgt es alle Variablen, die Sie zuweisen. Es weist jeder dieser Variablen eine Nummer aus einem lokalen monoton steigenden Integer zu. Wenn Python die Funktion ausführt, erstellt es ein Array mit so vielen Slots wie es lokale Variablen gibt, und weist jedem Slot einen speziellen Wert zu, der bedeutet "wurde noch nicht zugewiesen", und dort werden die Werte für diese Variablen gespeichert. Wenn Sie auf ein Lokales verweisen, das noch nicht zugewiesen wurde, sieht Python diesen speziellen Wert und wirft eine UnboundLocalValue-Ausnahme.

Die Bühne ist jetzt bereit. Ihr "from pprint import pprint" ist wirklich eine Form der Zuweisung. Python erstellt also eine lokale Variable namens "pprint", die die globale Variable verdeckt. Dann, wenn Sie in der Funktion auf "pprint.pprint" verweisen, treffen Sie auf den speziellen Wert und Python wirft die Ausnahme. Wenn Sie diese Importanweisung nicht in der Funktion hätten, würde Python die normale Auflösung nutzen, also zuerst in den Lokalen suchen und dann in den Globals und das pprint-Modul in den Globals finden.

Zur Klärung dieses Problems können Sie das Schlüsselwort "global" verwenden. Wahrscheinlich haben Sie Ihr Problem inzwischen schon gelöst, und ich weiß nicht, ob Sie wirklich "global" benötigt haben oder ob ein anderer Ansatz erforderlich war.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X