Python magic methods call method

Магический метод __call__. Функторы и классы-декораторы

На этом занятии мы познакомимся с очередным магическим методом __call__. Магические методы еще называют:

dunder-методы (от англ. сокращения double underscore)

В дальнейшем я буду говорить магические методы. Итак, когда вызывается метод __call__ и для чего он нужен? Как вы уже знаете, после объявления любого класса:

class Counter: def __init__(self): self.__counter = 0

Мы можем создавать его экземпляры командой:

Обратите внимание на круглые скобки после имени класса. В общем случае – это оператор вызова, например, так можно вызывать функции. Но, как видите, так можно вызывать и классы. В действительности, когда происходит вызов класса, то автоматически запускается магический метод __call__ и в данном случае он создает новый экземпляр этого класса:

Это очень упрощенная схема реализации метода __call__, в действительности, она несколько сложнее, но принцип тот же: сначала вызывается магический метод __new__ для создания самого объекта в памяти устройства, а затем, метод __init__ — для его инициализации. То есть, класс можно вызывать подобно функции благодаря встроенной для него реализации магического метода __call__. А вот экземпляры классов так вызывать уже нельзя. Если записать команду:

то возникнет ошибка: «TypeError: ‘Counter’ object is not callable».

Как вы уже догадались, мы можем поправить этот момент, если явно в классе Counter пропишем магический метод __call__, например, так:

class Counter: def __init__(self): self.__counter = 0 def __call__(self, *args, **kwargs): print("__call__") self.__counter += 1 return self.__counter

Здесь мы выводим сообщение, что был вызван данный метод, затем увеличиваем счетчик counter для текущего объекта на 1 и возвращаем его.

Запустим программу снова и теперь никаких ошибок нет, а в консоли отобразилась строка «__call__», что означает вызов магического метода __call__. То есть, благодаря добавлению этого магического метода в наш класс, теперь можно вызывать его экземпляры подобно функциям через оператор круглые скобки. Классы, экземпляры которых можно вызывать подобно функциям, получили название функторы.

В нашем случае метод __call__ возвращает значение счетчика, поэтому с объектом можно работать, следующим образом:

c = Counter() c() c() res = c() print(res)

Мы здесь три раза вызвали метод __call__ и счетчик __counter трижды увеличился на единицу. Поэтому в консоли мы видим значение 3. Мало того, если создать еще один объект-счетчик:

c = Counter() c2 = Counter() c() c() res = c() res2 = c2() print(res, res2)

То они будут работать совершенно независимо и подсчитывать число собственных вызовов.

Давайте еще раз посмотрим на определение метода __call__. Здесь записаны параметры *args, **kwargs. Это значит, что при вызове объектов мы можем передавать им произвольное количество аргументов. Например, в нашем случае можно указать значение изменения счетчика при текущем вызове. Для этого я перепишу метод __call__, следующим образом:

def __call__(self, step=1, *args, **kwargs): self.__counter += step return self.__counter

Здесь появился в явном виде первый параметр step с начальным значением 1. То есть, можно вызывать объекты, например, так:

Вот общий принцип работы магического метода __call__. Но здесь остается, как всегда, один важный вопрос: зачем это нужно, где может пригодиться? Давайте я приведу несколько примеров его использования.

Первый пример – это использование класса с методом __call__ вместо замыканий функций. Смотрите, мы можем объявить класс StripChars, который бы удалял вначале и в конце строки заданные символы:

class StripChars: def __init__(self, chars): self.__chars = chars def __call__(self, *args, **kwargs): if not isinstance(args[0], str): raise ValueError("Аргумент должен быть строкой") return args[0].strip(self.__chars)

Для этого, в инициализаторе мы сохраняем строку __chars – удаляемые символы, а затем, при вызове метода __call__ удаляем символы через строковый метод strip для символов __chars. То есть, теперь можно создать экземпляр класса и указать те символы, которые следует убирать:

А, затем, вызвать объект s1 подобно функции:

res = s1(" Hello World! ") print(res)

В результате объект s1 будет отвечать за удаление указанных символов в начале и конце строки. Но нам ничто не мешает определять другие объекты этого класса с другим набором символов:

s1 = StripChars(". ; ") s2 = StripChars(" ") res = s1(" Hello World! ") res2 = s2(" Hello World! ") print(res, res2, sep='\n')

То есть, объект s2 уже отвечает только за удаление пробелов, тогда как s1 и некоторых других символов. Достаточно элегантное решение задачи, где нам требуется сохранять символы для удаления.

Классы-декораторы

Второй пример – это реализация декораторов с помощью классов. Ранее мы с вами создавали декоратор для вычисления значения производной функции в определенной точке x. Я повторю эту реализацию, но с использованием класса. Вначале запишем следующий класс:

class Derivate: def __init__(self, func): self.__fn = func def __call__(self, x, dx=0.0001, *args, **kwargs): return (self.__fn(x + dx) - self.__fn(x)) / dx

Здесь в инициализаторе сохраняем ссылку на функцию, которую декорируем, а в методе __call__ принимаем один обязательный параметр x – точку, где вычисляется производная и dx – шаг изменения при вычислении производной.

Далее, определим функцию, например, просто синус:

def df_sin(x): return math.sin(x)

и вызове ее пока без декорирования:

После запуска программы увидим значение примерно 0.7071. Давайте теперь добавим декоратор. Это можно сделать двумя способами. Первый, прописать все в явном виде:

Теперь df_sin – это экземпляр класса Derivate, а не исходная функция. Поэтому, когда она будет вызываться, то запустится метод __call__ и вычислится значение производной в точке math.pi/4.

Второй способ – это воспользоваться оператором @ перед объявлением функции:

@Derivate def df_sin(x): return math.sin(x)

Получим абсолютно тот же самый результат. Вот принцип создания декораторов функций на основе классов. Как видите, все достаточно просто – запоминаем ссылку на функцию, а затем, расширяем ее функционал в магическом методе __call__.

Надеюсь, из этого занятия вы поняли, как работает метод __call__ и где он может быть использован. Конечно, я привел всего два простых примера, чтобы продемонстрировать принцип его работы. В реальных задачах, проектах, вы должны сами, используя свои знания, решать, какие механизмы, приемы следует применять для решения текущих задач. Привести алгоритмы решений на все случаи жизни просто невозможно – это уже навык алгоритмизации, которым должен владеть каждый программист. И вырабатывается он, в основном, на решении практических задач. Так что больше практикуйтесь параллельно с изучением возможностей языка Python.

Видео по теме

Концепция ООП простыми словами

#1. Классы и объекты. Атрибуты классов и объектов

#2. Методы классов. Параметр self

#3. Инициализатор __init__ и финализатор __del__

#4. Магический метод __new__. Пример паттерна Singleton

#5. Методы класса (classmethod) и статические методы (staticmethod)

#6. Режимы доступа public, private, protected. Сеттеры и геттеры

#7. Магические методы __setattr__, __getattribute__, __getattr__ и __delattr__

#9. Свойства property. Декоратор @property

#10. Пример использования объектов property

#11. Дескрипторы (data descriptor и non-data descriptor)

#12. Магический метод __call__. Функторы и классы-декораторы

#13. Магические методы __str__, __repr__, __len__, __abs__

#14 Магические методы __add__, __sub__, __mul__, __truediv__

#15. Методы сравнений __eq__, __ne__, __lt__, __gt__ и другие

#16. Магические методы __eq__ и __hash__

#17. Магический метод __bool__ определения правдивости объектов

#18. Магические методы __getitem__, __setitem__ и __delitem__

#19. Магические методы __iter__ и __next__

#20. Наследование в объектно-ориентированном программировании

#21. Функция issubclass(). Наследование от встроенных типов и от object

#22. Наследование. Функция super() и делегирование

#23. Наследование. Атрибуты private и protected

#24. Полиморфизм и абстрактные методы

#25. Множественное наследование

#27. Как работает __slots__ с property и при наследовании

#28. Введение в обработку исключений. Блоки try / except

#29. Обработка исключений. Блоки finally и else

#30. Распространение исключений (propagation exceptions)

#31. Инструкция raise и пользовательские исключения

#32. Менеджеры контекстов. Оператор with

#34. Метаклассы. Объект type

#35. Пользовательские метаклассы. Параметр metaclass

#36. Метаклассы в API ORM Django

#37. Введение в Python Data Classes (часть 1)

#38. Введение в Python Data Classes (часть 2)

#39. Python Data Classes при наследовании

© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта

Источник

Читайте также:  Python requests send image
Оцените статью