В этой статье рассказано о monkey patching (обезьяний патч см wiki), то есть о том, как динамически обновлять поведение кода во время выполнения. Мы также рассмотрим некоторые полезные примеры monkey patching в Python.
Это метод, используемый для динамического изменения поведения фрагмента кода во время выполнения.
Он позволяет изменить или расширять поведение библиотек, модулей, классов или методов во время выполнения без фактического изменения исходного кода.
Ситуации в которых может понадобиться monkey patching:
Monkey patching должен использоваться очень осторожно, особенно если он используются в производственном программном обеспечении (вообщем случае его не рекомендуется использовать, только если в этом есть крайняя необходимость). В общем случае его применения затрудняет предсказуемость поведения кода. Некоторые из причин почему его использование не рекомендуется:
В Python модули или классы похожи на любые другие изменяемые объекты, такие как списки, то есть мы можем изменять их или их атрибуты, включая функции или методы во время выполнения. Давайте рассмотрим несколько примеров.
В качестве основного примера давайте рассмотрим, как мы можем обновить атрибут модуля. Мы будем обновлять значение «пи» в модуле math, чтобы его точность была уменьшена до 3,14.
import math # Backup the original value before monkey patching original_pi = math.pi print(math.pi) # Output: 3.141592653589793 # Now monkey patch pi to have the value 3.14 math.pi = 3.14 print(math.pi) # Output: 3.14 # Remove the patch math.pi = original_pi print(math.pi) # Output: 3.141592653589793
Обратите внимание, как мы создали резервную копию исходного значения перед нашими вычислениями, а затем вернули исправление в конце. Это хорошая практика, особенно в тестах, чтобы не испортить весь набор тестов.
В этом примере мы увидим, как расширить поведение метода. Мы рассмотрим, как обновить встроенный метод печати в Python3, чтобы включить метку времени.
# Backup the original value before monkey patching original_print = print print(print) # Output: <built-in function print> print("Hey there!") # Output: Hey there! # Define our custom print to extend the original print with timestamps from datetime import datetime def custom_print(*args, **kwargs): original_print(datetime.utcnow(), *args, **kwargs) # Monkey patch builtin print method import builtins builtins.print = custom_print print(print) # Output: 2019-03-30 10:23:30.847503 <function custom_print at 0x10b22baba> print("Hey there!") # Output: 2019-03-30 10:23:30.847885 Hey there! # Remove the patch builtins.print = original_print print(print) # Output: <built-in function print> print("Hey there!") # Output: Hey there!
Теперь давайте посмотрим, как полностью изменить поведение метода. Это может быть особенно полезно в модульных тестах для моделирования сложных методов с внешними зависимостями (сеть, база данных и т. д.). Здесь мы рассмотрим, как заменить один метод другим.
# Original method def power(a, b): return a ** b # Mock method def mock_power(a, b): return "mock power" # Before monkey patching print(power(2, 4)) # Output: 16 # Monkey patch original method to replace it with the mock method power = mock_power # After monkey patching print(power(2, 4)) # Output: mock power
До сих пор мы обновляли атрибуты или методы на уровне модулей. Теперь давайте посмотрим, как обновить атрибут класса. Обратите внимание, что таким образом мы изменяем атрибут самого класса, поэтому все его экземпляры получат исправленный атрибут.
# Original class class Power(): # Original method def get(self, a, b): return a ** b # Mock method def mock_power(self, a, b): return "mock power" # Before monkey patching print(Power().get(2, 4)) # Output: 16 # Monkey patch original method to replace it with the mock method Power.get = mock_power # After monkey patching print(Power().get(2, 4)) # Output: mock power
В предыдущем примере показано, как обновить атрибут класса. В этом примере мы увидим, как обновить только атрибут конкретного экземпляра.
import types # Original class class Power(): # Original method def get(self, a, b): return a ** b # Mock method def mock_power(self, a, b): return "mock power" # Create a power instance and see how it works before monkey patching power_1 = Power() print(power_1.get(2, 4)) # Output: 16 print(power_1.get) # Output: <bound method Power.get of <__main__.Power object at 0x10bdcb0b8>> print(types.MethodType(power_1.get, power_1)) # Output: <bound method Power.get of <__main__.Power object at 0x10bdcb0b8>> # Note how MethodType method returns the type of method bound to the instance # Monkey patch the instance's method using types.MethodType print(types.MethodType(mock_power, power_1)) # Output: <bound method mock_power of <__main__.Power object at 0x10bdcb0b8>> power_1.get = types.MethodType(mock_power, power_1) print(power_1.get) # Output: <bound method mock_power of <__main__.Power object at 0x10bdcb0b8>> print(power_1.get(2, 4)) # Output: mock power # Create another instance and verify that its method is not patched power_2 = Power() print(power_2.get(3, 4)) # Output: 81 # The first instance stays patched print(power_1.get(3, 4)) # Output: mock power
Обратите внимание, что мы использовали метод MethodType из модуля types в Python, чтобы связать исправленный метод только с одним экземпляром. Это гарантирует, что другие экземпляры класса не будут затронуты.
Теперь давайте посмотрим, как мы могли бы изменить класс. Так как класс также является просто объектом, мы можем заменить его любым другим объектом.
# Original class class Hey: def hey(): print("Hey") # Mock class class Mocked: def hey(): print("You are mocked!") # Before monkey patching print(Hey.hey()) # Output: Hey # Monkey patch the original class with mock class Hey = Mocked # After monkey patching print(Hey.hey()) # Output: You are mocked!
В качестве последнего примера, давайте посмотрим, как мы можем заменить целый модуль. Это работает так же, как и с любым другим объектом в Python.
import math import json # Before monkey patching original_math = math print(math.__name__) # Output: math # Monkey patch match with json math = json print(math.__name__) # Output: json # Remove the patch math = original_math print(math.__name__) # Output: math
Monkey patching — это хорошая техника замены/обновления любых сущностей в Python. Однако, как мы уже говорили, он имеет свои недостатки и должен использоваться там где он реально необходим.
Оригинал: Monkey Patching in Python: Explained with Examples
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…