Другое

__init__ vs __call__ методы в Python: ключевые различия

Узнайте основные различия между методами __init__ и __call__ в Python: когда вызывается каждый, их цели и практические примеры для улучшения классов.

В чем разница между методами __init__ и __call__ в Python?

Например:

python
class test:
  def __init__(self):
    self.a = 10

  def __call__(self): 
    b = 20

Можете объяснить, когда и как каждый из этих методов используется, и какие у них отличительные назначения в классах Python?

Методы __init__ и __call__ в Python выполняют фундаментально разные роли в проектировании классов. Метод __init__ служит конструктором, который инициализирует состояние объекта при его создании, тогда как метод __call__ позволяет экземплярам вызываться как функции, сохраняя состояние между вызовами и выполняя операции динамически.

Содержание

Основные различия между init и call

Метод __init__ является эквивалентом конструктора в Python. Как объясняет Real Python, «Метод __init__ инициализирует новые объекты, сразу предоставляя им все необходимые свойства». Этот метод вызывается автоматически при создании нового экземпляра класса.

С другой стороны, метод __call__ превращает экземпляры в вызываемые объекты. По словам GeeksforGeeks, «Вызываемый объект – это объект, который можно вызывать как функцию. В Python __call__() используется для выполнения кода, связанного с вызываемым объектом».


Когда вызывается init

Метод __init__ вызывается один раз при создании нового экземпляра класса. Его основная цель – установить начальное состояние объекта.

python
class MyClass:
    def __init__(self, x):
        print("__init__ called")
        self.x = x  # Инициализация атрибута экземпляра

# Создание экземпляра вызывает __init__
obj = MyClass(42)
# Вывод: __init__ called

# obj.x теперь 42

Ключевые характеристики __init__:

  • Вызывается автоматически во время создания экземпляра
  • Используется для установки начального состояния и атрибутов
  • Не может возвращать значение (неявно возвращает None)
  • Вызывается только один раз за жизненный цикл объекта

Когда вызывается call

Метод __call__ вызывается каждый раз, когда вы используете экземпляр как функцию (поставив после него скобки).

python
class MyClass:
    def __init__(self, x):
        print("__init__ called")
        self.x = x
    
    def __call__(self, y):
        print("__call__ called")
        return self.x + y

# Создание экземпляра (вызывает __init__)
obj = MyClass(10)

# Использование экземпляра как функции (вызывает __call__)
result1 = obj(5)   # Вывод: __call__ called
result2 = obj(10)  # Вывод: __call__ called снова

print(result1)  # Вывод: 15
print(result2)  # Вывод: 20

Ключевые характеристики __call__:

  • Вызывается каждый раз, когда вы используете синтаксис instance()
  • Позволяет объектам вести себя как функции
  • Может вызываться несколько раз на одном и том же экземпляре
  • Может возвращать значения и принимать аргументы

Практические примеры

Состояние счётчика

python
class Counter:
    def __init__(self, initial=0):
        self.count = initial
    
    def __call__(self, increment=1):
        self.count += increment
        return self.count

# Использование
counter = Counter(5)
print(counter())      # Вывод: 6 (5 + 1)
print(counter(3))     # Вывод: 9 (6 + 3)
print(counter())      # Вывод: 10 (9 + 1)

Фильтрация файлов

python
import os

class FileFilter:
    def __init__(self, extensions):
        self.extensions = extensions
    
    def __call__(self, filename):
        base, ext = os.path.splitext(filename)
        return ext in self.extensions

# Использование
image_filter = FileFilter(('.jpg', '.png', '.gif'))
files = ['test.jpg', 'document.pdf', 'photo.png']
filtered_files = [f for f in files if image_filter(f)]
print(filtered_files)  # Вывод: ['test.jpg', 'photo.png']

Контроль доступа на основе ролей

python
class RoleChecker:
    def __init__(self, allowed_roles):
        self.allowed_roles = allowed_roles
    
    def __call__(self, user):
        if user.role not in self.allowed_roles:
            raise PermissionError("Access denied")
        return True

# Использование
admin_checker = RoleChecker(['admin', 'superuser'])
user = User(role='guest')
# admin_checker(user)  # Это вызовет PermissionError

Сценарии использования и приложения

Декораторы с параметрами

Как упомянуто в обсуждении на Reddit, __init__ и __call__ работают вместе, чтобы создавать декораторы, принимающие параметры:

python
class AuthMethod:
    def __init__(self, level):
        self.level = level
    
    def __call__(self, fn):
        def wrapper():
            print(f"Level: {self.level}")
            fn()
            print("Auth:", fn.__name__)
        return wrapper

# Использование
@AuthMethod("admin")
def process_document():
    print("Processing document...")

process_document()
# Вывод:
# Level: admin
# Processing document...
# Auth: process_document

Математические функции

python
class LinearEquation:
    def __init__(self, slope, intercept):
        self.slope = slope
        self.intercept = intercept
    
    def __call__(self, x):
        return self.slope * x + self.intercept

# Использование
line = LinearEquation(2, 3)
print(line(0))   # Вывод: 3
print(line(1))   # Вывод: 5
print(line(5))   # Вывод: 13

Кеширование/мемоизация

python
class CachedFunction:
    def __init__(self, func):
        self.func = func
        self.cache = {}
    
    def __call__(self, *args):
        if args not in self.cache:
            self.cache[args] = self.func(*args)
        return self.cache[args]

# Использование
@CachedFunction
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # Вычисляет и кэширует
print(fibonacci(10))  # Возвращает из кэша

Сводка сравнения

Аспект __init__ __call__
Время вызова Вызывается один раз при создании экземпляра Вызывается каждый раз, когда экземпляр вызывается
Назначение Инициализация состояния объекта Позволяет объекту вести себя как функция
Аргументы Принимает аргументы конструктора Принимает аргументы, похожие на параметры функции
Возвращаемое значение Неявно возвращает None Может возвращать любое значение
Использование obj = MyClass(args) result = obj(args)
Сохранение состояния Устанавливает начальное состояние Может изменять и сохранять состояние между вызовами

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

  1. Используйте __init__ для инициализации объекта: задавайте атрибуты экземпляра, проверяйте входные параметры и устанавливайте начальное состояние.
  2. Используйте __call__ для функционального поведения: когда вы хотите, чтобы объекты действовали как функции, сохраняя состояние между вызовами.
  3. Разделяйте обязанности: __init__ отвечает за инициализацию, а __call__ – за функциональность.
  4. Учитывайте потокобезопасность: если ваш метод __call__ изменяет состояние, будьте внимательны к проблемам многопоточности при использовании объекта в конкурентных контекстах.

Распространённые ошибки

  1. Смешивание ролей: помните, что __init__ вызывается один раз при создании, а __call__ – каждый раз при вызове экземпляра как функции.
  2. Чрезмерное использование __call__: не каждый класс должен быть вызываемым. Реализуйте __call__ только тогда, когда это действительно оправдано.
  3. Забывание вызвать __init__ родителя: при наследовании не забывайте вызывать __init__ родительского класса, если это необходимо.
  4. Проблемы управления состоянием: будьте осторожны с управлением состоянием в методе __call__, особенно при работе с изменяемыми объектами.

Ключевой вывод: __init__ создаёт и инициализирует объекты, а __call__ даёт им возможность вызываться как функции, сохраняя состояние и контекст. Это мощное сочетание позволяет создавать сложные, состояний-ориентированные вызываемые объекты в Python.

Источники

  1. Real Python - Python’s .call() Method: Creating Callable Instances
  2. GeeksforGeeks - What is the difference between init and call?
  3. Stack Overflow - What is the difference between init and call?
  4. Reddit - init and call In Python - How They Differ And What They Do
  5. Python Course - Callable Instances of Classes
  6. Medium - Using call method to invoke class instance as a function
  7. Towards Data Science - Python Callables: The Basics and the Secrets

Заключение

  • __init__ – конструктор, инициализирующий объекты один раз при создании.
  • __call__ делает экземпляры вызываемыми, позволяя им вести себя как функции с сохранением состояния.
  • Используйте __init__ для установки начального состояния, а __call__ – для реализации функционального поведения.
  • Вместе они позволяют создавать мощные паттерны, такие как состояний-ориентированные функции, декораторы с параметрами и механизмы кеширования.
  • Понимание этих методов помогает проектировать более гибкие и переиспользуемые классы Python.
Авторы
Проверено модерацией
Модерация