Scope von localen Funktionen

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
fonzy
User
Beiträge: 9
Registriert: Mittwoch 18. Januar 2012, 17:21

Weiß jemand von Euch wie sich der Scope von lokal definierten Funktionen aufbaut?
Folgender Code wirft eine Exception:

Code: Alles auswählen

def outer():
    a=5
    def inner():
        a+=1
    inner()
>>> outer()
UnboundLocalError: local variable 'a' referenced before assignment
Wenn ich jedoch den Integer in einer Liste verstecke gehts?!?

Code: Alles auswählen

def outer():
    a=[5]
    def inner():
        a[0]+=1
    inner()
Warum kennt die lokale Funktion 'a' nicht wenn es ein Integer ist sondern nur wenn es ein Objekt ist?

Könnt ihr mir das erklären? Was mach ich im ersten Beispiel falsch? Ist CodeSchnipsel 2 tatsächlich die Lösung?

Danke
deets

Das liegt daran, dass du im ersten Beispiel einen Namen an ein Objekt bindest - und das ist das einzige, was Python als Hinweis darauf hat dass du eine Variable deklarierst.

Im zweiten Beispiel modifiezierst du ein mutable Objekt durch einen existierenden Namen. Darum beschwert Python sich da nicht.

In Python 3 gibt es fuer diesen Fall das nonlocal-Keyword.
lunar

@fonzy: Siehe Naming and Binding in der Sprachreferenz.

Tatsächlich ist das zweite Beispiel die Lösung, wenn man den übergeordneten lokalen Namensraum verändern möchte. Um die Lesbarkeit zu verbessern, würde ich allerdings eher ein Wörterbuch oder ein Dummy-Objekt nutzen:

Code: Alles auswählen

def outer():
    ns = {'a': 5}
    def inner():
        ns['a'] += 1
    inner()

def outer():
    ns = object()
    ns.a = 5
    def inner():
        ns.a += 1
    inner()
deets

@lunar

Das hast du aber nicht wirklich ausprobiert, oder ;)

Code: Alles auswählen

for nice experiences hit <tab> multiple times
>>> f = object()
>>> f.foo = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'
>>> 
Ich wuenschte auch es gaebe da eine so simple loesung, um sich das gefummele mit dict/anfuehrungsstrichen und so zu sparen.
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Na ja, zwölf Zeilen Code für so eine Möglichkeit …

Code: Alles auswählen

class Mutable(object):

    def __init__(self):
        self.variables = {}

    def __setattr__(self, name, value):
        self.__dict__[name] = value

    def __getattr__(self, name):
        if name not in self.__dict__:
            raise AttributeError
        return self.__dict__[name]
Wenn man es unbedingt haben will, ist das ja nicht unbedingt zu viel verlangt.
lunar

@deets: Stimmt, das habe ich nicht. Ich bin überrascht, dass sich die Semantik von "object()" so sehr unterscheidet von Klassen, die man von "object" ableitet.

@nomnom: Es geht wesentlich kürzer:

Code: Alles auswählen

In [5]: ns = type('ns', (object,), {})()

In [6]: ns.foo = 10
deets

@lunar

das liegt wohl daran, dass object auch superklasse von C-typen sein soll ala int, die ja auch kein instance-dict kennen.

Wir haben hier ein util-Pakte, in dem uA ein Bunch vorhanden ist - der eignet sich natuerlich hervorragend fuer sowas. Aber auch das ist schon eine (kleine) Abhaengigkeit.
lunar

@deets: Stimmt, daran hatte ich nicht gedacht.

Btw, nette Signatur ;)
fonzy
User
Beiträge: 9
Registriert: Mittwoch 18. Januar 2012, 17:21

Vielen Dank für Eure Hilfe :)

Aber bevor ich mir eine Iterable-Klasse für sowas mache scheint mir doch ein Integer-Objekt mit entsprechend überladenen Operatoren fast sauberer zu sein. Aber dann haben wir ja das gleiche Problem wie bei Java, wo ich ein Integer in ein IntegerObjekt umwandeln muss, um es per Referenz an eine Methode zu übergeben...
problembär

fonzy hat geschrieben:Aber bevor ich mir eine Iterable-Klasse für sowas mache scheint mir doch ein Integer-Objekt mit entsprechend überladenen Operatoren fast sauberer zu sein. Aber dann haben wir ja das gleiche Problem wie bei Java, wo ich ein Integer in ein IntegerObjekt umwandeln muss, um es per Referenz an eine Methode zu übergeben...
Ich glaube, sowas wird man nie machen müssen, nur um ein paar Daten zu verarbeiten. Alles viel zu kompliziert. Wahrscheinlich ist es besser, einfacher zu denken.
deets

@fonzy

Wieso eine iterable Klasse? Das hat doch damit nix zu tun. Alles was du brauchst ist ein mutierbares Objekt. Klar, das ist ein Workaround, aber bis Py3 ist's halt notwendig.
lunar

@fonzy: Worauf willst Du mit "iterable Klasse" hinaus? Um dieses Problem zu lösen, brauchst Du keine eigene Klasse, und erst recht keine, die einen iterierbaren Datentyp definiert.

Deine Idee, einfach von "int" abzuleiten, und die Operatoren zu überladen, funktioniert ohnehin nicht, da +=, -= und Konsorten den Namen auf der linken Seite immer im aktuellen Namensraum neu binden, unabhängig davon, ob der Typ des rechten Operanden den Operator überlädt oder nicht:

Code: Alles auswählen

In [1]: class Foo(object):
   ...:     def __iadd__(self, value):
   ...:         print(value)
   ...:         return self
   ...: 

In [2]: f = Foo()

In [3]: f += 10
10

In [4]: def foo():
   ...:     f += 10
   ...: 

In [5]: foo()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
/home/swiesner/<ipython-input-5-624891b0d01a> in <module>()
----> 1 foo()

/home/swiesner/<ipython-input-4-5ab5d0e3f155> in foo()
      1 def foo():
----> 2     f += 10
      3 

UnboundLocalError: local variable 'f' referenced before assignment
Anders darf sich Python an dieser Stelle gar nicht verhalten, da eine "__iadd__()"-Implementierung prinzipiell beliebige Objekte zurückgeben kann, so dass der Name zwangsläufig neu gebunden werden muss, um eine korrekte Semantik zu erhalten.
fonzy
User
Beiträge: 9
Registriert: Mittwoch 18. Januar 2012, 17:21

Sorry, mein fehler. Ich hab mir eingebildet, nomnom hätte in seinem Vorschlag

__getitem__ und __setitem__

implementiert. Ne iterables brauchen wir wahrlich nicht...
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

nomnom hat geschrieben:Noch kürzer:

Code: Alles auswählen

ns = lambda: Null
Wie komm ich denn auf Null? Ich meine natürlich None …
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Guten morgen...

Ich hoffe mal, dass das jetzt nicht völlig am Thema vorbeigeht... ;-) Aber ist es nicht grundsätzlich sinnvoller, die getrennten Namensräume zu berücksichtigen, anstatt zu versuchen sie zu überbrücken?

Code: Alles auswählen

def outer(a):
    def inner(a):
        a += 1
        return a
    return inner(a)
Welches Szenario könnte es denn geben, dass ich über ein Dummy-Objekt auf den oberen Namensraum zugreifen muss?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Ganz allgemein ist das in Closures üblich.
Antworten