Для динамической модификации функций в библиотеке с помощью декораторов:
Пример:
import os
def log_execution(func):
def wrapper(*args, **kwargs):
print(f"Вызов функции: {func.__name__}")
result = func(*args, **kwargs)
print(f"Результат: {result}")
return result
return wrapper
#Условие для применения декоратора
USE_LOGGING = os.environ.get("ENABLE_LOGGING", "False").lower() == "true"
def apply_decorators(library):
for name, obj in library.__dict__.items():
if callable(obj) and USE_LOGGING:
setattr(library, name, log_execution(obj))
import my_library # Наша библиотека
apply_decorators(my_library) # Применяем декораторы
Этот подход позволяет гибко настраивать поведение библиотеки без изменения ее исходного кода.
Декораторы в Python - мощный инструмент для динамической модификации поведения функций и методов. Применительно к библиотекам, они позволяют добавлять функциональность к существующим функциям, не изменяя их исходный код. Это особенно полезно, когда нужно расширить или адаптировать поведение функций, предоставляемых библиотекой, под конкретные нужды проекта, или когда необходимо добавить функциональность к функциям, которые мы не контролируем напрямую (например, функции из сторонней библиотеки).
Основные подходы и примеры:
1. Добавление логирования: Представим, что мы хотим логировать вызовы функций из некоторой библиотеки:
import logging
logging.basicConfig(level=logging.INFO)
def log_calls(func):
def wrapper(*args, **kwargs):
logging.info(f"Вызов функции: {func.__name__} с аргументами: {args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"Функция {func.__name__} вернула: {result}")
return result
return wrapper
# Пример использования (предположим, что 'some_library.some_function' существует):
import some_library
some_library.some_function = log_calls(some_library.some_function)
# Теперь каждый вызов some_library.some_function будет логироваться
some_library.some_function(1, 2)
Здесь мы переопределяем функцию в библиотеке, заменяя её декорированной версией. Важно отметить, что это влияет на *все* использования some_library.some_function
в программе.
2. Кэширование результатов: Для функций, возвращающих одни и те же результаты при одинаковых входных данных, полезно кэширование:
import functools
def memoize(func):
cache = {}
@functools.wraps(func) # Сохраняем метаданные исходной функции
def wrapper(*args, **kwargs):
key = (args, tuple(sorted(kwargs.items()))) # Ключ для кэша
if key in cache:
return cache[key]
else:
result = func(*args, **kwargs)
cache[key] = result
return result
return wrapper
# Пример использования (предположим, что 'another_library.expensive_function' существует):
import another_library
another_library.expensive_function = memoize(another_library.expensive_function)
# Теперь вызовы another_library.expensive_function будут кэшироваться
print(another_library.expensive_function(3, 4)) # первый вызов
print(another_library.expensive_function(3, 4)) # второй вызов (из кэша)
functools.wraps
важен для сохранения метаданных исходной функции (__name__
, __doc__
и т.д.). Это обеспечивает более прозрачную замену.
3. Валидация аргументов:
def validate_arguments(validator_func):
def decorator(func):
def wrapper(*args, **kwargs):
if not validator_func(*args, **kwargs):
raise ValueError("Неверные аргументы")
return func(*args, **kwargs)
return wrapper
return decorator
def is_positive_numbers(*args, **kwargs):
return all(isinstance(arg, (int, float)) and arg > 0 for arg in args)
# Пример использования:
import yet_another_library
yet_another_library.process_data = validate_arguments(is_positive_numbers)(yet_another_library.process_data)
# Теперь функция process_data проверит, что аргументы положительные числа
try:
yet_another_library.process_data(5, 10)
yet_another_library.process_data(-1, 10) # Вызовет ValueError
except ValueError as e:
print(f"Ошибка: {e}")
4. Замена атрибутов (monkey patching):
class CustomClass:
def original_method(self):
print("Original method")
def new_method(self):
print("Modified method")
CustomClass.original_method = new_method
obj = CustomClass()
obj.original_method() # Выведет "Modified method"
Этот подход заменяет метод класса непосредственно. Следует использовать с осторожностью, так как это может привести к непредсказуемым побочным эффектам.
Важные замечания:
В заключение, декораторы предоставляют гибкий способ модификации функций в библиотеках. Однако, их использование требует осторожности и тщательного планирования, чтобы избежать нежелательных побочных эффектов и обеспечить стабильность кода.