Метод __call__ вызывается при обращении к экземпляру как к функции. Это не повторяющееся определение – если метод __call__ присутствует, интерпретатор будет вызвать его, когда экземпляр вызывается как функция, передавая ему любые позиционные и именованные аргументы:
1 2 3 4 5 6 7 8 9 10 11 |
>>> class CallMe: def __call__(self, *args, **kwargs): # Реализует вызов экземпляра print('Called:', args, kwargs) # Принимает любые аргументы ... ... >>> C = CallMe() >>> C(4,5,6) Called: (4,5,6) {} >>> c(5,6,7, x = 10, y = 20) Called: (5,6,7) {'x' : 10, 'y': 20 } |
Выражаясь более формальным языком, метод __call__ поддерживает все схемы передачи аргументов. Все что передается экземпляру, передается этому методу наряду с обычным аргументом self, в котором передается сам экземпляр. Например, следующие определения метода:
1 2 3 4 5 6 7 8 9 10 11 |
class C: def __call__(self, a, b, x=50, y=60): # Обычные и со значениями по умолчанию pass class C: def __call__(self, *args, **kwargs): # Произвольные аргументы pass class C: def __call__(self, *args, x = 100, **kwargs): # Аргументы которые могут pass # передаваться только по имени |
соответствует следующим вызовам экземпляра:
1 2 3 4 5 6 |
X = C() X(1, 2) # Аргументы со значениями по умолчанию опущены X(1, 2, 3, 4) # Позиционные X(a = 10, b = 20, c = 30) # Именованные X(*[1, 2], **dict(c=3, d=4)) # Распаковывание произвольных аргументов X(1, *(2,), c=3, **dict(d=4)) # Смешанные режимы |
Суть состоит в том, что классы и экземпляры, имеющие метод __call__, поддерживают тот же синтаксис и семантику передачи аргументов, что и обычные функции и методы.
Реализация операции вызова, как в данном примере, позволяет экземплярам классов имитировать поведение функций, а также сохранять информацию о состоянии между вызовами.
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> class Prod: def __init__(self, value): # Принимает единственный аргумент self.value = value def __call__(self, other): return self.value * other ... ... >>> X = Prod(3) # Запоминает 3 в своей области видимости >>> X(3) # 3 (передано) * 3 (сохраненное значение) 9 >>> X(4) 12 |
В следующим примере реализация метода __call__ может показаться ненужной. То же самое поведение можно реализовать с помощью простого метода:
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> class Prod: def __init__(self, value): # Принимает единственный аргумент self.value = value def myFunc(self, other): return self.value * other ... ... >>> X = Prod(3) # Запоминает 3 в своей области видимости >>> X.myFunc(3) # 3 (передано) * 3 (сохраненное значение) 9 >>> X.myFunc(4) 12 |
Однако метод __call__ может оказаться удобнее при взаимодействии с прикладными интерфейсами, где ожидается функция – это позволяет создавать объекты, совместимые с интерфейсами, ожидающими получить функцию, которые к тому же способны сохранять информацию о своем состоянии между вызовами.
Фактически этот метод занимает третье место среди наиболее часто используемых методов перезагрузки операторов – после конструктора __init__ и методов форматирования __str__ и __repr__.
Где выгодно купить просмотры ВК на записи и видео? И сколько они стоят? Когда высокая цена оправдана и обоснована? Почему без денег не всегда хорошо? Сайт https://pricesmm.com/ предлагает результаты анализа платных способов и цен на просмотры Вконтакте.
Для наших читателей хочу показать хороший пример где наглядно показывается удобность метода __call__. В следующем примере мы создали класс который определяет факториал использую кэш а также рекурсивность.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Factorial: def __init__(self): # Конструктор не принимает аргументов self.cache = {} # Создаем пустой словарь для кэша def __call__(self, n): if n not in self.cache: if n == 0: self.cache[n] = 1 else: self.cache[n] = n * self.__call__(n-1) # Рекурсия! return self.cache[n] fact = Factorial() |
Проверяем работоспособность нашего класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
for i in range(10): print("{}! = {}".format(i, fact(i))) # fact(i) вызывается __call__ # вывод 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 |