Ich habe Probleme, eine Funktion aus einem anderen Modul durch eine andere Funktion zu ersetzen, und das macht mich verrückt.
Angenommen, ich habe ein Modul bar.py, das so aussieht:
from a_package.baz import do_something_expensive
def a_function():
print do_something_expensive()
Und ich habe ein anderes Modul, das so aussieht:
from bar import a_function
a_function()
from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
a_function()
import a_package.baz
a_package.baz.do_something_expensive = lambda: 'Something really cheap.'
a_function()
Ich würde erwarten, die Ergebnisse zu erhalten:
Something expensive!
Something really cheap.
Something really cheap.
Aber stattdessen bekomme ich folgendes:
Something expensive!
Something expensive!
Something expensive!
Was mache ich falsch?
python
monkeypatching
Guidoismus
quelle
quelle
from module import non_module_member
und Affen-Patches auf Modulebene sind aus diesem Grund nicht kompatibel und werden im Allgemeinen am besten vermieden.apackage
. H.from foo import bar
ist jedoch in Ordnung und wird gegebenenfalls empfohlen.Antworten:
Es kann hilfreich sein, darüber nachzudenken, wie Python-Namespaces funktionieren: Sie sind im Wesentlichen Wörterbücher. Wenn Sie dies tun:
from a_package.baz import do_something_expensive do_something_expensive = lambda: 'Something really cheap.'
Betrachten Sie es so:
do_something_expensive = a_package.baz['do_something_expensive'] do_something_expensive = lambda: 'Something really cheap.'
Hoffentlich können Sie erkennen , warum dies nicht funktioniert , dann :-) Wenn Sie einen Namen in einem Namensraum zu importieren, wird der Wert des Namens im Namensraum Sie importiert aus irrelevant ist. Sie ändern den Wert von do_something_expensive nur im Namespace des lokalen Moduls oder im Namespace von a_package.baz oben. Da Bar do_something_expensive direkt importiert, anstatt es aus dem Modul-Namespace zu referenzieren, müssen Sie in seinen Namespace schreiben:
import bar bar.do_something_expensive = lambda: 'Something really cheap.'
quelle
Dafür gibt es einen wirklich eleganten Dekorateur: Guido van Rossum: Python-Dev-Liste: Monkeypatching Idioms .
Es gibt auch das Dectools- Paket, bei dem ich eine PyCon 2010 gesehen habe, die möglicherweise auch in diesem Kontext verwendet werden kann, aber das könnte auch in die andere Richtung gehen (Monkeypatching auf der Ebene der Methodendeklaration ... wo Sie nicht sind).
quelle
Wenn Sie es nur für Ihren Anruf patchen und ansonsten den Originalcode beibehalten möchten, können Sie https://docs.python.org/3/library/unittest.mock.html#patch (seit Python 3.3) verwenden:
with patch('a_package.baz.do_something_expensive', new=lambda: 'Something really cheap.'): print do_something_expensive() # prints 'Something really cheap.' print do_something_expensive() # prints 'Something expensive!'
quelle
Im ersten Snippet
bar.do_something_expensive
verweisen Sie auf das Funktionsobjekt,a_package.baz.do_something_expensive
auf das in diesem Moment verwiesen wird. Um wirklich "monkeypatch" zu machen, müssten Sie die Funktion selbst ändern (Sie ändern nur, worauf sich die Namen beziehen); Das ist möglich, aber das wollen Sie eigentlich nicht.Bei Ihren Versuchen, das Verhalten von zu ändern
a_function
, haben Sie zwei Dinge getan:Im ersten Versuch machen Sie do_something_expensive zu einem globalen Namen in Ihrem Modul. Sie rufen jedoch auf
a_function
, das in Ihrem Modul nicht nach Auflösungsnamen sucht, sodass es immer noch auf dieselbe Funktion verweist.Im zweiten Beispiel ändern Sie das, worauf es sich
a_package.baz.do_something_expensive
bezieht, sind aberbar.do_something_expensive
nicht magisch daran gebunden. Dieser Name bezieht sich immer noch auf das Funktionsobjekt, nach dem es beim Initialisieren gesucht hat.Der einfachste, aber alles andere als ideale Ansatz wäre, sich zu ändern, um
bar.py
zu sagenimport a_package.baz def a_function(): print a_package.baz.do_something_expensive()
Die richtige Lösung ist wahrscheinlich eines von zwei Dingen:
a_function
, um eine Funktion als Argument zu verwenden und dies zu nennen, anstatt zu versuchen, sich einzuschleichen und zu ändern, auf welche Funktion es schwer zu codieren ist, oderDie Verwendung von Globals (das ist es, was das Ändern von Dingen auf Modulebene von anderen Modulen ist) ist eine schlechte Sache , die zu nicht wartbarem, verwirrendem, nicht testbarem, nicht skalierbarem Code führt, dessen Fluss schwer zu verfolgen ist.
quelle
do_something_expensive
in dera_function()
Funktion ist nur eine Variable im Namespace des Moduls, die auf ein Funktionsobjekt verweist. Wenn Sie das Modul neu definieren, tun Sie dies in einem anderen Namespace.quelle