Prashant Sharma
Prashant SharmaSoftware Engineer

Czym jest monkey patching w Pythonie?

Dowiedz się, czym jest monkey patching w Pythonie i jak z niego korzystać, aby sobie nie zaszkodzić.
17.07.20203 min
Czym jest monkey patching w Pythonie?

Termin monkey patching w Pythonie odnosi się do dynamicznych modyfikacji klasy lub modułu w czasie uruchamiania. Oznacza to, że monkey patch to fragment kodu, który rozszerza lub modyfikuje inny kod podczas wykonania programu. Monkey patching jest możliwy tylko w językach dynamicznych, których dobrym przykładem jest tutaj Python. Polega on na ponownym otwieraniu istniejących klas lub metod podczas uruchamiania kodu i zmienianiu ich zachowania. Należy jednak postępować tutaj ostrożnie oraz korzystać z tego tylko, jeśli naprawdę musisz. 

Ponieważ Python jest dynamicznym językiem programowania, to klasy są modyfikowalne. Można je dzięki temu ponownie otwierać i zmieniać, czy też nawet zastępować czymś innym. Monkey patching służy również do zastępowania lub rozszerzania metody na poziomie modułu, lub klasy, za pomocą niestandardowej implementacji.

Zaimplementujmy więc to:

class MonkeyPatch:
    def __init__(self, num):
        self.num = num
    def addition(self, other):
        return (self.num + other)
obj = MonkeyPatch(10)
obj.addition(20)


Wynik: 

30


Jak widać poniżej, tylko metody __init__ oraz addition są dostępne dla powyższego obiektu klasy (możesz również użyć dir(obj), aby uzyskać składowe obiektu).

import inspect 
inspect.getmembers(obj, predicate=inspect.ismethod)


Wynik:

[('__init__',
  <bound method MonkeyPatch.__init__ of <__main__.MonkeyPatch object at 0x7f32495d7c50>>),
 ('addition',
  <bound method MonkeyPatch.addition of <__main__.MonkeyPatch object at 0x7f32495d7c50>>)]


W powyższym kodzie zdefiniowaliśmy klasę MonkeyPatch, która ma metodę addition. Następnie dodajemy nową metodę do klasy MonkeyPatch, która może wyglądać tak:

def subtraction(self, num2):
    return self.num - num2


Gdy dodajemy powyższą metodę do klasy MonkeyPatch, to umieszczamy funkcję subtraction w klasie MonkeyPatch za pomocą instrukcji przypisania:

MonkeyPatch.subtraction = subtraction


Nowa funkcja subtraction będzie dostępna dla wszystkich istniejących i nowych instancji. Przejdźmy do praktyki:

import inspect
inspect.getmembers(obj, predicate=inspect.ismethod)


Wynik:

[('__init__',
  <bound method MonkeyPatch.__init__ of <__main__.MonkeyPatch object at 0x7f28186b8c88>>),
 ('addition',
  <bound method MonkeyPatch.addition of <__main__.MonkeyPatch object at 0x7f28186b8c88>>),
 ('subtraction',
  <bound method subtraction of <__main__.MonkeyPatch object at 0x7f28186b8c88>>)]


Czy zauważyliście, że istniejący obiekt obj, który zdefiniowaliśmy wcześniej, zawierał nowo utworzoną funkcję? Sprawdźmy, czy będzie ona działać dla istniejącej instancji:

>>> obj.subtraction(1)               # Working as expected
9
>>> obj_1 = MonkeyPatch(10)          # create some new object
>>> obj_1.subtraction(2)
8

obj.subtraction działa zgodnie z oczekiwaniami, ale należy pamiętać, że jeśli istnieje już metoda o tej samej nazwie, to spowoduje ona zmianę zachowania istniejącej metody.

O czym warto pamiętać

Najlepszą rzeczą byłoby niekorzystanie z monkey patchingu. Jeżeli chcesz zmienić zachowanie, możesz zdefiniować klasy podrzędne. Mimo to, jeśli potrzebujesz monkey patchingu, to postępuj zgodnie z poniższymi zasadami:

  1. Używaj go, jeśli masz naprawdę dobry powód (np. tymczasowa krytyczna poprawka)
  2. Napisz odpowiednią dokumentację opisującą powód, dla którego użyłeś monkey patchingu
  3. Dokumentacja powinna również zawierać informacje o usunięciu monkey patcha i to, na co należy zwrócić uwagę. Wiele takich rozwiązań jest tymczasowa, więc usunięcie ich powinno być proste.
  4. Postaraj się, aby monkey patch był jak najbardziej wyeksponowany. Umieść też kod tego rozwiązania w osobnych plikach. 

Podsumowanie

Oto, jak powinno się używać monkey patchingu w Pythonie. Korzystanie z tego rozwiązania ma jednak swoje wady i należy do tego podchodzić ostrożnie. 

Użycie monkey patchingu często oznacza to złą architekturę aplikacji, no i nie jest dobrą decyzją z punktu widzenia projektu, ponieważ stwarza rozbieżność między oryginalnym kodem źródłowym na dysku a obserwowanym zachowaniem. Co więcej, może to być bardzo mylące podczas rozwiązywania pewnych problemów.


Oryginał tekstu w języku angielskim możesz przeczytać tutaj.

<p>Loading...</p>