История вопроса:
В Python всё является объектом, и любая функция — тоже объект. Был реализован механизм, который позволяет экземпляру класса вести себя как функция. Для этого был введён специальный магический метод call, который позволяет сделать объекты вызываемыми.
Проблема:
Иногда требуется передать объект, который может выполнять действия при вызове, например, функции с состоянием (стейтфул функции), замыкания, объекты-обработчики событий и т.п. Разработка архитектуры требует понимания, как правильно реализовать такую функциональность.
Решение:
Реализуя в классе метод call, можно сделать его экземпляр вызываемым "как функцию". Это позволяет объединить возможности классов (инкапсуляция состояния, наследование, методы) и функций (вызываемость). Такой подход используется для создания объектов-команд, сложных обработчиков, обёрток и т.п.
Пример кода:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # Выведет 15
Ключевые особенности:
Унаследует ли метод call атрибуты обычной функции — например, name и doc?
Нет, у объекта с методом call атрибут name будет отсутствовать (или будет брать от класса). Метаданные функций не сохраняются.
Является ли объект с реализованным call настоящей функцией?
Нет, это экземпляр класса, а не функция. Он только реализует "вызываемое" поведение. Например, его сравнение с функцией через isinstance(obj, types.FunctionType) даст False.
Можно ли применить к объекту с call декоратор, предназначенный для функции?
Обычно такие декораторы ожидают именно функцию, а не объект (например, functools.lru_cache). Использование может привести к ошибкам или не сработать вовсе.
Плюсы:
Негативный кейс: В проекте реализовали логгер через класс с call, чтобы хранить настройки (уровень, имя файла). Однако забыли, что функции-обработчики сигналов ожидают именно функцию, и получали ошибки при регистрации обработчика (object is not a function).
Плюсы: гибкая настройка логгера. Минусы: несовместимость с ожидаемыми интерфейсами.
Положительный кейс: В другом проекте использовали класс с call для создания сложных функций-декораторов, сохраняя параметры, что упростило тестирование.
Плюсы: расширяемость, удобство. Минусы: больше кода по сравнению с функцией или lambda.