Seite 1 von 1

Spaß mit Dekoratoren

Verfasst: Mittwoch 28. Juni 2023, 20:59
von pillmuncher

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()

Re: Spaß mit Dekoratoren

Verfasst: Mittwoch 15. November 2023, 23:37
von mm96
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

Re: Spaß mit Dekoratoren

Verfasst: Donnerstag 16. November 2023, 00:58
von __blackjack__
@mm96: Zahlen sind nicht veränderbar, das heisst `foo()` macht mit dem `x` nichts und da kann man auch nichts rückgängig machen.

Re: Spaß mit Dekoratoren

Verfasst: Donnerstag 16. November 2023, 01:06
von pillmuncher
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.

Re: Spaß mit Dekoratoren

Verfasst: Donnerstag 16. November 2023, 08:52
von kbr
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.

Re: Spaß mit Dekoratoren

Verfasst: Dienstag 19. Dezember 2023, 22:34
von mm96
Ok, ich glaub ich habs endlich kapiert :D.
Danke Euch!