← Назад к вопросам

В каких ситуациях применяется monkey patching

1.8 Middle🔥 71 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Monkey Patching в Python

Monkey patching — это динамическое изменение поведения класса или модуля во время выполнения программы. Эта техника спорная и часто опасна, но иногда необходима.

Что это

class Calculator:
    def add(self, a, b):
        return a + b

calc = Calculator()
print(calc.add(2, 3))  # 5

def new_add(self, a, b):
    return a + b + 100

Calculator.add = new_add
calc = Calculator()
print(calc.add(2, 3))  # 105

Ситуация 1: Тестирование

import requests
from unittest import mock

class UserService:
    def get_user(self, user_id):
        response = requests.get(f"https://api.example.com/users/{user_id}")
        return response.json()

def test_get_user():
    with mock.patch("requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
        service = UserService()
        user = service.get_user(1)
        assert user["name"] == "Alice"

Ситуация 2: Исправление bug в библиотеке

import third_party_library

original_func = third_party_library.process_data

def patched_process_data(data):
    if isinstance(data, str):
        data = data.strip()
    return original_func(data)

third_party_library.process_data = patched_process_data

Ситуация 3: Добавление методов к стандартным типам

original_count = str.count

def count_case_insensitive(self, substring):
    return original_count(self.lower(), substring.lower())

str.count_insensitive = count_case_insensitive

text = "Hello HELLO hello"
print(text.count_insensitive("hello"))  # 3

Ситуация 4: Dependency injection для тестирования

class Database:
    def connect(self):
        return psycopg2.connect("dbname=prod")

class TestDatabase:
    def connect(self):
        return sqlite3.connect(":memory:")

Database = TestDatabase
db = Database()
conn = db.connect()

Ситуация 5: Исправление несовместимости версий

import json_lib

original_parse = json_lib.parse

def compatible_parse(data):
    try:
        return original_parse(data)
    except ValueError:
        return None

json_lib.parse = compatible_parse

Ситуация 6: Логирование и мониторинг

class PaymentService:
    def process_payment(self, amount):
        return amount * 1.1

original_process = PaymentService.process_payment

def logged_process_payment(self, amount):
    print(f"Processing: {amount}")
    result = original_process(self, amount)
    print(f"Result: {result}")
    return result

PaymentService.process_payment = logged_process_payment

Опасные примеры

class UserRepository:
    def get_all(self):
        return []

UserRepository.get_all = lambda self: [{"id": 1}]

# Проблемы:
# 1. Непредсказуемое поведение
# 2. Сложно отладить
# 3. Конфликты в разных модулях
# 4. Невозможно отследить изменения

Лучшие практики

# Используем наследование вместо patching
class CalculatorV2(Calculator):
    def add(self, a, b):
        return super().add(a, b) + 100

# Dependency Injection
class Service:
    def __init__(self, calculator=None):
        self.calculator = calculator or Calculator()

# Контекстный менеджер — patch только внутри блока
with mock.patch("module.function", new=replacement):
    pass
# После блока всё вернулось в норму

Когда можно использовать

  • В тестах (pytest fixtures, mock)
  • Для срочного исправления критических bagов
  • Для совместимости версий (временно)
  • Для добавления логирования/мониторинга в боевом коде

Когда НЕЛЬЗЯ

  • В production коде без очень веской причины
  • Для изменения основной логики
  • Без документирования
  • В глобальном скоупе где много модулей

Лучше использовать наследование, Dependency Injection или явный patching в тестах. Monkey patching — это инструмент для экстренных ситуаций, не для повседневной разработки.