Вызываемые объекты: практическое руководство
Основные понятия и способы создания
Базовый способ: функция callable() и метод __call__
Вызываемый объект в Python любой объект, который поддерживает операцию вызова с круглыми скобками. Для проверки применяется встроенная функция callable(). Она возвращает True, если объект может быть вызван. К вызываемым относятся функции, классы, методы, а также любые объекты, у которых определен метод __call__. Пример проверки:
def my_func():
pass
print(callable(my_func)) # True
print(callable(42)) # FalsePython callable object (вызываемый объект в python)
True False
Int object python (объект int в python)
Создать вызываемый объект проще всего с помощью обычной функции. Для создания объекта класса с возможностью вызова необходимо определить в классе метод __call__.
Типичная ошибка: вызов невызываемого объекта приводит к TypeError: 'int' object is not callable. Перед вызовом рекомендуется проверять callable().
Как сделать экземпляр класса вызываемым через метод __call__?
В классе определяется метод __call__. Экземпляр такого класса становится вызываемым и сохраняет состояние.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
double = Multiplier(2)
print(double(5)) # 10
print(double(7)) # 14
встроенные типы python (встроенные типы python)
10 14
Проблема: если __call__ ожидает аргументы, а при вызове их не передают, возникает TypeError. Следует проверять сигнатуру метода.
Как создать анонимную вызываемую функцию с помощью lambda?
Лямбда-выражение возвращает вызываемую анонимную функцию. Подходит для простых операций.
add = lambda a, b: a + b
print(callable(add)) # True
print(add(3, 4)) # 7
True 7
Лямбда не содержит инструкций и сложной логики. Для многострочных функций применяется def.
Как зафиксировать аргументы функции, создав новый вызываемый объект с помощью functools.partial?
functools.partial возвращает вызываемый объект, который подставляет заранее заданные аргументы.
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
print(callable(square)) # True
print(square(5)) # 25
True 25
Ошибка: если передать позиционные аргументы в неправильном порядке, возможна путаница. Рекомендуется использовать именованные аргументы.
Как создать декоратор, который оборачивает функцию и возвращает вызываемый объект?
Декоратор принимает функцию и возвращает новую вызываемую обертку. Важно вернуть функцию, иначе объект не будет вызываемым.
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__} с args={args} kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name):
return f"Привет, {name}!"
print(callable(greet)) # True
print(greet("Анна"))
Вызов greet с args=('Анна',) kwargs={}
Привет, Анна!
Если забыть вернуть внутреннюю функцию из декоратора (например, вернуть None), получится невызываемый объект. Проверка callable() помогает вовремя обнаружить ошибку.
Как использовать метод класса как вызываемый объект?
Метод класса при обращении через экземпляр или класс является вызываемым. Такой метод можно передавать в качестве аргумента.
class Calculator:
def add(self, a, b):
return a + b
calc = Calculator()
method = calc.add
print(callable(method)) # True
print(method(3, 5)) # 8
True 8
Разница между методом экземпляра и функцией: метод автоматически передает self. При использовании в качестве аргумента высшего порядка это следует учитывать.
Какие встроенные типы являются вызываемыми?
Функции (def, lambda, built-in), классы (вызов класса создает экземпляр), методы, объекты с __call__. Некоторые типы не вызываемы: int, str, list, dict и т.д.
print(callable(list)) # True (класс)
print(callable(int)) # True (класс)
print(callable("hello")) # False
True True False
Путаница между классом и экземпляром: класс вызываем, экземпляр - только если определен __call__.
class CallCounter:
def __init__(self):
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Вызов {self.count}: args={args}, kwargs={kwargs}")
counter = CallCounter()
print(callable(counter)) # True
counter("a", b=2)
counter(1, 2, 3)
print(f"Итого вызовов: {counter.count}")
True
Вызов 1: args=('a',) kwargs={'b': 2}
Вызов 2: args=(1, 2, 3) kwargs={}
Итого вызовов: 2
class Curry:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self, *more_args, **more_kwargs):
combined_args = self.args + more_args
combined_kwargs = {**self.kwargs, **more_kwargs}
return self.func(*combined_args, **combined_kwargs)
def multiply(a, b, c):
return a * b * c
curried = Curry(multiply, 2)
print(curried(3, 4)) # 2*3*4 = 24
print(callable(curried)) # True
24 True
import time
def timed(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
wrapper.total_time += elapsed
wrapper.calls += 1
print(f"{func.__name__} заняла {elapsed:.6f} секунд")
return result
wrapper.total_time = 0.0
wrapper.calls = 0
return wrapper
@timed
def slow_add(a, b):
time.sleep(0.01)
return a + b
print(callable(slow_add)) # True
print(slow_add(1, 2))
print(slow_add(3, 4))
print(f"Всего вызовов: {slow_add.calls}, общее время: {slow_add.total_time:.6f}")
True slow_add заняла 0.010001 секунд 3 slow_add заняла 0.010020 секунд 7 Всего вызовов: 2, общее время: 0.020021
handlers = {}
def register_handler(name, handler):
if not callable(handler):
raise ValueError(f"Handler {name} is not callable")
handlers[name] = handler
def my_handler():
print("Обработчик сработал")
register_handler("test", my_handler)
register_handler("lambda", lambda: 42)
try:
register_handler("invalid", 123)
except ValueError as e:
print(e)
Handler invalid is not callable
import math
print(callable(math)) # False (модуль не вызываем)
print(callable(math.sqrt)) # True (функция)
False True