Spaß mit Dekoratoren

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
pillmuncher
User
Beiträge: 1481
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: 12940
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.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
pillmuncher
User
Beiträge: 1481
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: 1486
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