Spaß mit Dekoratoren

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Code: Alles auswählen

def foo(x, y, z, register_undo):

    # irgendwas mit x, y, z machen
    
    @register_undo
    def undo_foo():
        # irgendwas mit x, y, z wieder rückgängig machen


def main():
    undo_stack = []
    ...
    foo(a, b, c, undo_stack.append)
    ...
    undo_stack.pop()()


if __name__ == "__main__":
    main()
In specifications, Murphy's Law supersedes Ohm's.
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Hi,

ich verstehe Dein Beispiel nicht richtig, ich kriegs auch nicht zum Laufen.
Z. Bsp. der Code

Code: Alles auswählen

def foo(x, y, z, register_undo):

    x +=5
    
    @register_undo
    def undo_foo():
        x -=5


def main():
    a, b, c = 1, 2, 3

    undo_stack = []
    
    foo(a, b, c, undo_stack.append)
    
    undo_stack.pop()()


if __name__ == "__main__":
    main()
wirft den Error in Zeile 7:

Code: Alles auswählen

UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
Könntest Du mir da auf die Sprünge helfen?

LG
Benutzeravatar
__blackjack__
User
Beiträge: 13129
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mm96: Zahlen sind nicht veränderbar, das heisst `foo()` macht mit dem `x` nichts und da kann man auch nichts rückgängig machen.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Das x in foo() und das x in undo_foo() sind nicht dasselbe x. Python kennt keinen Lexical Scope. Vergleiche:

Code: Alles auswählen

# modul xyz

x = 1

def bar():
    x = 2
    print(x)

bar()

print(x)
Ergebnis:Das funktioniert, weil die Variable x in bar() bei der Zuweisung "entsteht". Dein x -= 5 bedeutet aber: "Nimm die bereits lokal bestehende Variable x, subtrahiere 5 und weise den Wert anschließend wieder x zu. Das geht natürlich nicht, weil im lokalen Scope kein x existiert. Du lönntest natürlich eine nonlocal Anweisung verwenden, was aber ggf. den Programmfluss ziemlich unübersichtlich macht. Der übliche Weg, wie man in Python langlebige Werte verwendet, ist mit Klassen und Objekten:

Code: Alles auswählen

class Baz:
    def __init__(self):
        self.x = 123
        self._undo_list = []
    def undo(self):
        if self._undo_list:
            self._undo_list.pop()
    def foo(self):
        self.x += 5
        @self._undo_list.append
        def undo_foo():
            self.x -= 5
            
b = Baz()
print(b.x)  # -> 123
b.foo()
print(b.x)  # -> 128
b.undo()
print(b.x)  # -> 123
Ungetestet.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Um zur Verwirrung beizutragen: Aufgrund der closure ist Zugriff auf x möglich:

Code: Alles auswählen

    @register_undo
    def undo_foo():
        print(x)
Eine Dekrementierung via `x -= 5` ist jedoch mit der Erzeugung des Labels `x` im lokalen Scope verbunden und das führt dann zu dem genannten Fehler. Dem lässt sich mit `nonlocal` beikommen (wie pillmuncher schon schrieb) mit der entsprechenden Konfusion, die damit einhergehen kann.
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Ok, ich glaub ich habs endlich kapiert :D.
Danke Euch!
Antworten