Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Назначение метода call в Python
__call__ — это специальный метод (dunder method), который делает объект класса вызываемым как функция. Это один из самых мощных и недооценённых инструментов Python.
Основной концепт
# Обычный объект
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says: Woof!")
dog = Dog("Rex")
dog.bark() # Вызываем метод
# Rex says: Woof!
# С __call__: объект становится вызываемым
class SmartDog:
def __init__(self, name):
self.name = name
def __call__(self):
print(f"{self.name} says: Woof!")
smart_dog = SmartDog("Max")
smart_dog() # Вызываем объект как функцию!
# Max says: Woof!
# Это то же самое, что
smart_dog.__call__()
# Max says: Woof!
Использование 1: Декораторы
Декораторы часто используют __call__:
# Простой декоратор — это класс с __call__
class Timer:
"""Декоратор, который измеряет время выполнения функции"""
def __init__(self, func):
self.func = func
self.calls = 0
self.total_time = 0
def __call__(self, *args, **kwargs):
import time
self.calls += 1
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
self.total_time += (end - start)
print(f"Executed {self.func.__name__} in {end - start:.4f}s")
return result
@Timer
def slow_function(n):
"""Вычисляет факториал"""
import time
time.sleep(0.1)
result = 1
for i in range(1, n + 1):
result *= i
return result
# Использование
result = slow_function(5) # Вызываем функцию, как обычно
# Executed slow_function in 0.1023s
print(f"Total time: {slow_function.total_time:.2f}s")
print(f"Calls: {slow_function.calls}")
Использование 2: Strategy Pattern
# Переключаемые стратегии обработки
class PaymentProcessor:
"""Базовая стратегия обработки платежей"""
def __call__(self, amount: float) -> bool:
raise NotImplementedError
class CreditCardProcessor(PaymentProcessor):
def __init__(self, card_number: str):
self.card_number = card_number
def __call__(self, amount: float) -> bool:
print(f"Processing ${amount} with credit card {self.card_number[-4:]}")
# Логика обработки
return True
class PayPalProcessor(PaymentProcessor):
def __init__(self, account: str):
self.account = account
def __call__(self, amount: float) -> bool:
print(f"Processing ${amount} with PayPal account {self.account}")
# Логика обработки
return True
# Использование
def checkout(processor: PaymentProcessor, amount: float):
"""Принимает любую стратегию платежа"""
if processor(amount): # Вызываем через __call__
print("Payment successful")
else:
print("Payment failed")
cc_processor = CreditCardProcessor("1234-5678-9012-3456")
checkout(cc_processor, 99.99)
# Processing $99.99 with credit card 3456
# Payment successful
paypal = PayPalProcessor("user@example.com")
checkout(paypal, 49.99)
# Processing $49.99 with PayPal account user@example.com
# Payment successful
Использование 3: Функциональные объекты
# Объект, который ведёт себя как функция, но хранит состояние
class Counter:
"""Счётчик, который считает вызовы"""
def __init__(self, start=0):
self.count = start
def __call__(self, increment=1):
self.count += increment
return self.count
counter = Counter(0)
print(counter()) # 1
print(counter(5)) # 6
print(counter()) # 7
print(counter(10)) # 17
# Функция с состоянием!
# Обычная функция не может хранить состояние между вызовами
Использование 4: Фабрики объектов
# Объект, который создаёт другие объекты
class DatabaseFactory:
"""Фабрика для создания подключений БД"""
def __init__(self, driver):
self.driver = driver
self.connections = []
def __call__(self, host: str, port: int, database: str):
"""Вызов фабрики создаёт новое соединение"""
connection_string = f"{self.driver}://{host}:{port}/{database}"
print(f"Creating connection: {connection_string}")
self.connections.append(connection_string)
return connection_string
# Использование
db_factory = DatabaseFactory("postgresql")
conn1 = db_factory("localhost", 5432, "mydb") # Создаёт соединение
# Creating connection: postgresql://localhost:5432/mydb
conn2 = db_factory("192.168.1.100", 5432, "proddb") # Создаёт ещё одно
# Creating connection: postgresql://192.168.1.100:5432/proddb
print(f"Total connections created: {len(db_factory.connections)}")
# Total connections created: 2
Использование 5: Middleware и Pipeline
# Объекты, которые обрабатывают данные в цепочке
class Middleware:
"""Базовый middleware"""
def __init__(self, next_handler=None):
self.next_handler = next_handler
def __call__(self, request):
raise NotImplementedError
class LoggingMiddleware(Middleware):
def __call__(self, request):
print(f"[LOG] Request: {request}")
if self.next_handler:
return self.next_handler(request)
return request
class AuthMiddleware(Middleware):
def __call__(self, request):
if 'token' not in request:
raise ValueError("No authentication token")
print(f"[AUTH] Token verified")
if self.next_handler:
return self.next_handler(request)
return request
class ProcessingMiddleware(Middleware):
def __call__(self, request):
print(f"[PROCESS] Processing request data")
request['processed'] = True
if self.next_handler:
return self.next_handler(request)
return request
# Собираем цепочку
pipeline = LoggingMiddleware(
AuthMiddleware(
ProcessingMiddleware()
)
)
# Использование
request = {'data': 'hello', 'token': 'abc123'}
result = pipeline(request) # Вызов идёт через __call__
# [LOG] Request: {'data': 'hello', 'token': 'abc123'}
# [AUTH] Token verified
# [PROCESS] Processing request data
Использование 6: Карриирование (Currying)
# Функция, которая принимает аргументы поэтапно
class Curried:
"""Карриированная функция"""
def __init__(self, func, *args):
self.func = func
self.args = args
def __call__(self, *new_args):
combined_args = self.args + new_args
return Curried(self.func, *combined_args)
def __repr__(self):
"""Для красивого вывода"""
if len(self.args) == 0:
return f"Curried({self.func.__name__})"
try:
return self.func(*self.args)
except TypeError:
return f"Curried({self.func.__name__}, {self.args})"
# Пример
def add(a, b, c):
return a + b + c
add_curried = Curried(add)
# Можем вызвать постепенно
result1 = add_curried(1)(2)(3)
print(result1) # 6
# Или все сразу
result2 = add_curried(1, 2, 3)
print(result2) # 6
Практический пример: Валидатор с состоянием
from typing import Any, Callable
class Validator:
"""Валидатор данных, который можно переиспользовать"""
def __init__(self, rules: list[Callable]):
self.rules = rules
self.errors = []
def __call__(self, data: Any) -> bool:
self.errors = []
for rule in self.rules:
try:
rule(data)
except ValueError as e:
self.errors.append(str(e))
return len(self.errors) == 0
# Определяем правила
def is_not_empty(data):
if not data:
raise ValueError("Data cannot be empty")
def is_positive(data):
if isinstance(data, (int, float)) and data <= 0:
raise ValueError("Data must be positive")
def is_string(data):
if not isinstance(data, str):
raise ValueError("Data must be string")
# Использование
name_validator = Validator([is_not_empty, is_string])
age_validator = Validator([is_not_empty, is_positive])
# Валидируем
if name_validator("John"):
print("Name is valid")
else:
print(f"Name errors: {name_validator.errors}")
if age_validator(25):
print("Age is valid")
else:
print(f"Age errors: {age_validator.errors}")
if age_validator(-5):
print("Age is valid")
else:
print(f"Age errors: {age_validator.errors}")
# Age errors: ['Data must be positive']
Когда использовать call
| Сценарий | Используйте call? |
|---|---|
| Декораторы | ✅ Да (часто) |
| Strategy pattern | ✅ Да |
| Объекты с состоянием | ✅ Да |
| Фабрики | ✅ Да |
| Middleware/Pipeline | ✅ Да |
| Функции высшего порядка | ✅ Да |
| Просто функция | ❌ Нет, используйте def |
Вывод
__call__ превращает объект в вызываемую сущность, что позволяет:
- Комбинировать данные (состояние) с поведением
- Создавать элегантные API
- Реализовать сложные паттерны проектирования
- Писать более Pythonic код
Объекты с __call__ часто более мощные и гибкие, чем обычные функции, потому что они могут хранить контекст и состояние между вызовами.