2 Stimmen

Problem mit Monkeypatching-Methoden und Referenzen

Ich habe mich gefragt, ob jemand dieses Problem erklären und eine Lösung anbieten kann:

$ cat object-override-methods.py 
class A:
    def foo(self):
        return 1

class B:
    def foo(self):
        return 1

for klass in A, B:
    orig_foo = klass.foo
    def foo(self):
        return orig_foo(self) * 2
    klass.foo = foo

A().foo()
B().foo()
$ python object-override-methods.py
Traceback (most recent call last):
  File "object-override-methods.py", line 15, in <module>
    A().foo()
  File "object-override-methods.py", line 12, in foo
    return orig_foo(self) * 2
TypeError: unbound method foo() must be called with B instance as first argument (got A instance instead)

Vielen Dank im Voraus.

2voto

RichieHindle Punkte 256891

Denn orig_foo auf globaler Ebene definiert ist, trampeln Sie jedes Mal in der Schleife auf seinem Wert herum. Dieser getrampelte Wert wird dann von jedem Ihrer neuen foo Methoden.

Eine einfache Lösung besteht darin, den Code in eine Funktion zu verschieben, etwa so:

def rebind_foo(klass):
    orig_foo = klass.foo
    def foo(self):
        return orig_foo(self) * 2
    klass.foo = foo

for klass in A, B:
   rebind_foo(klass)

Dadurch wird sichergestellt, dass jede neue foo Methode erhält ihren eigenen Wert von orig_foo .

2voto

unutbu Punkte 769083

orig_foo ist eine globale Variable, deren Wert sich bei jedem Durchlauf der Schleife ändert. Nachdem die Schleife beendet ist, orig_foo bezieht sich auf B.foo .

Die inneren Funktionen foo (einer oder jeder Durchlauf durch die Schleife) verwenden beide den globalen Wert für orig_foo wenn sie aufgerufen werden. Sie rufen also beide B.foo(self) .

Beim Aufruf einer "ungebundenen Methode" wie orig_foo überprüft Python2, ob das erste Argument eine Instanz der entsprechenden Klasse ist. A().foo() besteht diese Prüfung nicht. (Interessanterweise wurde diese Prüfung in Python3 entfernt, so dass kein TypeError ausgelöst wird und dieser Fehler schwieriger zu finden sein könnte).

Um dies zu beheben, müssen Sie den Wert von orig_foo an die entsprechende klass . Sie können dies tun, indem Sie orig_foo eine lokale Variable von foo . Eine Möglichkeit, dies zu tun, besteht darin, die orig_foo ein Argument von foo mit einem Standardwert. Python bindet die Standardwerte zum Zeitpunkt der Definition einer Funktion. Also orig_foo=orig_foo bindet die lokale Variable orig_foo auf den aktuellen Wert des klass.foo :

for klass in A, B:
    orig_foo = klass.foo
    def foo(self, orig_foo=orig_foo):
        return orig_foo(self) * 2
    klass.foo = foo

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X