Unerwarteter 'UnboundLocalError'

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Hallo zusammen,

ich bin gerade auf ein mir merkwürdiges Verhalten gestoßen:

Code: Alles auswählen

#!/usr/bin/env python3
# coding: utf-8

class Foo:
    pass

def main():
    print(Foo)

if __name__ == '__main__':
    main()
Gibt, wie erwartet folgendes aus:

Code: Alles auswählen

<class '__main__.Foo'>
Der folgende Code wirft ein UnboundLocalError:

Code: Alles auswählen

#!/usr/bin/env python3
# coding: utf-8

class Foo:
    pass

def main():
    print(Foo)
    del Foo # +++

if __name__ == '__main__':
    main()

Code: Alles auswählen

Traceback (most recent call last):
  File "foo_boom.py", line 12, in <module>
    main()
  File "foo_boom.py", line 8, in main
    print(Foo)
UnboundLocalError: local variable 'Foo' referenced before assignment
Wenn ich das richtig interpretiere, wird das del-Statement vor der print-Funktion aufgerufen. :K Oder wie lässt sich das Verhalten anders (besser) erklären?

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Code: Alles auswählen

In [6]: dis.dis(main)
  2           0 LOAD_GLOBAL              0 (Foo)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE        

In [7]: dis.dis(main2)
  2           0 LOAD_FAST                0 (Foo)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  3           5 DELETE_FAST              0 (Foo)
              8 LOAD_CONST               0 (None)
             11 RETURN_VALUE 
Wobei `LOAD_FAST` einen lokalen Namen laedt. Der Grund dafuer ist ganz einfach: Vor der Ausfuehrung gibt es eine Analyse (eben bei der Erzeugung von obigem Bytecode), wird der Namensraum in einer Funktion veraendert, hier durch das Entfernen, geht man von einem lokalen Namen aus und das fuer die ganze Funktion. Mit `global Foo` kannst du den "Fehler" beheben.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Danke für Deine Aufklärung. Das dis-Modul sollte ich wirklich öfter (überhaupt mal) verwenden. Es ist durchaus interessant zu erfahren, wie der Python-Compiler manchmal tickt.

Aber ich habe schon auf die Tapete vor mir in großen Lettern gemalt: "Du willst keine globalen Klassen löschen!"

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein ähnliches Verhalten lässt sich übrigens auch hiermit erzeugen:

Code: Alles auswählen

foo = 'foo'

def bar():
    print(foo)
    foo = 'bar'
Es ist ganz einfach so, dass auf globale Variablen bzw auf alles, was oberhalb des aktuellen Sichtbereiches liegt, nur lesender Zugriff möglich ist. Bei schreibendem Zugriff wird stattdessen (wie ja schon erwähnt wurde) eine lokale Variable angelegt, die schon von Beginn an im lokalen Scope (Sichtbereich) gültig ist. Deshalb meckert der Interpreter rum, da du mit `print(foo)` auf etwas zugreifst, das an dieser Stelle - zumindest im lokalen Kontext der Funktion - noch gar nicht existiert.

Würde man die Zeile mit dem `print()` löschen, dann gäbe es keine Fehlermeldung. Allerdings würde in diesem Fall auch nicht wirklich was passieren, da dann nur innerhalb der Funktion eine Variable `foo` erstellt wurde, welche den String `'bar'` zugewiesen bekommen hat und auf die nach Verlassen der Funktion kein Zugriff mehr möglich ist. An der lokalen Variable `foo` (bzw deren Wert) hätte sich dadurch nichts verändert.

Vielleicht wird der Sachverhalt so ja noch etwas klarer für dich. ;)
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

bwbg hat geschrieben:Aber ich habe schon auf die Tapete vor mir in großen Lettern gemalt: "Du willst keine globalen Klassen löschen!"
snafu hat es schon erwaehnt, aber damit es nicht untergeht: Das ist nicht speziell bei Klassen, sondern gilt fuer alle Namen.
Antworten