Mam problem z zastąpieniem funkcji z innego modułu inną funkcją i doprowadza mnie to do szału.
Powiedzmy, że mam moduł bar.py, który wygląda następująco:
from a_package.baz import do_something_expensive
def a_function():
print do_something_expensive()
Mam inny moduł, który wygląda tak:
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()
Spodziewałbym się wyników:
Something expensive!
Something really cheap.
Something really cheap.
Ale zamiast tego otrzymuję to:
Something expensive!
Something expensive!
Something expensive!
Co ja robię źle?
python
monkeypatching
guidoizm
źródło
źródło
from module import non_module_member
z tego powodu małpa i łatanie na poziomie modułu są niekompatybilne i ogólnie najlepiej ich unikać.apackage
.from foo import bar
jest dobrze i faktycznie zalecane, gdy stosowne.Odpowiedzi:
Warto pomyśleć o tym, jak działają przestrzenie nazw Pythona: są to w zasadzie słowniki. Więc kiedy to zrobisz:
from a_package.baz import do_something_expensive do_something_expensive = lambda: 'Something really cheap.'
Pomyśl o tym w ten sposób:
do_something_expensive = a_package.baz['do_something_expensive'] do_something_expensive = lambda: 'Something really cheap.'
Mam nadzieję, że zdajesz sobie sprawę, dlaczego to nie działa wtedy :-) Po zaimportowaniu nazwę w obszarze nazw, wartość nazwy w obszarze nazw ty importowanego z ma znaczenia. Zmieniasz tylko wartość do_something_expensive w przestrzeni nazw modułu lokalnego lub w przestrzeni nazw a_package.baz powyżej. Ale ponieważ bar importuje do_something_expensive bezpośrednio, zamiast odwoływać się do niego z przestrzeni nazw modułu, musisz napisać do jego przestrzeni nazw:
import bar bar.do_something_expensive = lambda: 'Something really cheap.'
źródło
Jest do tego naprawdę elegancki dekorator: Guido van Rossum: Python-Dev list: Monkeypatching Idioms .
Istnieje również pakiet dectools , który widziałem w PyCon 2010, który może być również używany w tym kontekście, ale może to faktycznie pójść w drugą stronę (małpa dopasowywanie na poziomie deklaratywnym metody ... gdzie nie jesteś)
źródło
Jeśli chcesz załatać go tylko dla swojego wywołania, a poza tym pozostawić oryginalny kod, możesz użyć https://docs.python.org/3/library/unittest.mock.html#patch (od Pythona 3.3):
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!'
źródło
W pierwszym fragmencie
bar.do_something_expensive
odwołujesz się do obiektu funkcji,a_package.baz.do_something_expensive
do którego odwołuje się w tym momencie. Aby naprawdę "monkeypatch", musiałbyś zmienić samą funkcję (zmieniasz tylko to, do czego odnoszą się nazwy); jest to możliwe, ale tak naprawdę nie chcesz tego robić.Próbując zmienić zachowanie programu
a_function
, zrobiłeś dwie rzeczy:W pierwszej próbie ustawiasz w swoim module globalną nazwę do_something_expensive. Jednak dzwonisz
a_function
, co nie szuka w Twoim module nazw, więc nadal odwołuje się do tej samej funkcji.W drugim przykładzie zmieniasz to,
a_package.baz.do_something_expensive
do czego się odnosi, alebar.do_something_expensive
nie jesteś z tym magicznie związany. Ta nazwa nadal odnosi się do obiektu funkcji, który wyszukał podczas inicjalizacji.Najprostszym, ale daleko od idealnych rozwiązaniem byłoby zmienić
bar.py
, aby powiedziećimport a_package.baz def a_function(): print a_package.baz.do_something_expensive()
Prawidłowe rozwiązanie to prawdopodobnie jedna z dwóch rzeczy:
a_function
aby przyjąć funkcję jako argument i wywołać ją, zamiast próbować wkraść się i zmienić funkcję, do której trudno jest się odnieść, lubUżywanie globali (tym jest zmiana elementów na poziomie modułu z innych modułów) jest złą rzeczą, która prowadzi do niemożliwego do utrzymania, zagmatwania, nietestowalnego, nieskalowalnego kodu, którego przepływ jest trudny do śledzenia.
źródło
do_something_expensive
wa_function()
funkcji jest po prostu zmienną w przestrzeni nazw modułu wskazującą na obiekt funkcji. Kiedy przedefiniowujesz moduł, robisz to w innej przestrzeni nazw.źródło