Декораторы в Python предоставляют элегантный способ для работы с зависимостями и внедрения зависимостей, особенно когда мы хотим избежать явной передачи объектов зависимостей в каждую функцию или класс, которые в них нуждаются. Вместо этого, декораторы могут выступать в роли контейнера или фабрики, обеспечивая эти зависимости автоматически.
Вот несколько способов использования декораторов для управления зависимостями:
Этот подход позволяет передавать объекты зависимостей в декоратор, который затем внедряет их в декорируемую функцию. Это особенно полезно для конфигурирования поведения функции или класса, основываясь на внешних зависимостях.
def with_dependency(dependency):
def decorator(func):
def wrapper(*args, **kwargs):
# Здесь dependency внедряется в func
return func(dependency, *args, **kwargs)
return wrapper
return decorator
class MyDependency:
def do_something(self):
return "Dependency in action!"
@with_dependency(MyDependency())
def my_function(dep):
print(dep.do_something())
my_function()
Декораторы можно использовать для регистрации функций или классов как поставщиков (providers) зависимостей. Затем, другой декоратор может "разрешить" эти зависимости, получая их из зарегистрированных поставщиков и внедряя в нужные функции или классы.
dependency_registry = {}
def provider(key):
def decorator(func):
dependency_registry[key] = func
return func
return decorator
def inject(dependencies):
def decorator(func):
def wrapper(*args, **kwargs):
injected_deps = {}
for key, dep_name in dependencies.items():
if dep_name in dependency_registry:
injected_deps[key] = dependency_registry[dep_name]() # Instantiate the dependency
else:
raise ValueError(f"Dependency {dep_name} not found in registry")
return func(*args, **kwargs, **injected_deps)
return wrapper
return decorator
@provider("database_connection")
def get_database_connection():
# Simulate creating a database connection
return "Database Connection"
@provider("logger")
def get_logger():
return "Logger Instance"
@inject({"db": "database_connection", "log": "logger"})
def my_service(db, log):
print(f"Using: {db} and {log}")
my_service()
Декораторы могут инкапсулировать контекст и конфигурацию, необходимые для работы функции. Например, можно создать декоратор, который устанавливает параметры конфигурации перед вызовом функции и восстанавливает их после выполнения.
def configure_environment(config_values):
def decorator(func):
def wrapper(*args, **kwargs):
original_values = {}
for key, value in config_values.items():
original_values[key] = os.environ.get(key)
os.environ[key] = value
try:
result = func(*args, **kwargs)
finally:
# Restore original values
for key, value in original_values.items():
if value is None:
del os.environ[key]
else:
os.environ[key] = value
return result
return wrapper
return decorator
import os
@configure_environment({"API_KEY": "YOUR_API_KEY"})
def call_api():
api_key = os.environ.get("API_KEY")
print(f"Calling API with key: {api_key}")
call_api()
Преимущества использования декораторов для внедрения зависимостей:
Недостатки:
В целом, декораторы предоставляют мощный и гибкий механизм для работы с зависимостями и инъекциями в Python-приложениях. Они позволяют создавать более чистый, читаемый и поддерживаемый код, но требуют аккуратного использования и хорошего понимания их работы.