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

Зачем нужен метод __call__ в Python?

2.0 Middle🔥 61 комментариев
#Python Core

Комментарии (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__ часто более мощные и гибкие, чем обычные функции, потому что они могут хранить контекст и состояние между вызовами.