Функторы в Python
Функциональные объекты, или функторы в программировании — объекты, которые можно вызывать, подобно функциям. В этой статье мы разберемся, как создавать функторы и какие возможности они открывают.
Как создать функтор?
Как известно, объекты в Python являются экземплярами класса. Значит, для создания функторов нам тоже нужен класс. Однако обычно объекты нельзя вызвать.
a = 1
# TypeError: 'int' object is not callable
Для того чтобы расширить функциональность объекта, нам понадобится переопределить магический метод __call__
. Он отвечает за поведение объекта при вызове.
class Functor:
def __call__(self, *args):
print('Вызов функтора с аргументами:', *args)
functor = Functor()
functor(1, 'q')
# Вызов функтора с аргументами: 1 q
При вызове наш функтор выполняет ровно то, что мы записали в метод __call__
. Но что толку от функтора, если он заменяется простой функцией? Ведь нам доступны все возможности, которые можно реализовать с помощью класса!
Функтор, как замена замыкания и частичного применения функции
В статье о замыканиях мы упоминали, что их можно заменить на функторы. Давайте возьмем оттуда функцию для подбора имени ребенка, перепишем в виде функтора и улучшим.
class SetNames:
def __init__(self, names):
self.names = []
self.results = dict()
self.add(names)
def __call__(self, surname):
result = self.results.get(surname)
if self.results == dict():
self.results.update({surname: []})
for name in self.names:
self.results[surname].append(
f'{name} {surname}'
)
result = self.results[surname]
return result
def add(self, lst: list):
for i in lst:
if not isinstance(i, str):
raise TypeError(
'Имена должны быть строками'
)
self.results.clear()
self.names.extend(lst)
girl_names = SetNames(('Julia', 'Lisa'))
girl_names.add(['Inna', 'Anna'])
print(girl_names('Koval'))
# ['Julia Koval', 'Lisa Koval', 'Inna Koval', 'Anna Koval']
Наш функтор лишен проблем с поздним связыванием, характерных для замыканий. Теперь мы можем на ходу добавить в него парочку новых имен. Кроме того, результаты сохраняются, и если мы запросим список для той же фамилии снова, то сразу получим готовый результат.
Классы-декораторы
Поскольку функторы можно вызывать, само собой напрашивается использование их классов в роли декораторов.
from datetime import datetime
class Validation:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
res = self.function(*args, **kwargs)
if not 0 < res < 100:
raise ValueError(
'Validation failed'
)
return f'{datetime.now()}: {res}'
@Validation
def square(a):
return a**2
print(square(8))
# 2022-10-29 09:24:29.503697: 64
print(square(12))
# ValueError: Validation failed
Конструктор нашего класса-декоратора принимает только один аргумент — функцию. В методе __call__
мы проверяем, находится ли возвращаемое значение в диапазоне от 0
до 100
. Если условие не выполняется, мы вызываем ошибку, а иначе просто возвращаем строку с текущим временем и полученным числом. Но можно пойти дальше, и сделать декоратором объект класса.
class Validation:
def __init__(self, lower, upper):
self._lower = lower
self._upper = upper
def __call__(self, arg):
def wrapper(a):
res = arg(a)
if not self._lower < res < self._upper:
raise ValueError(
'Validation failed'
)
return f'{datetime.now()}: {res}'
return wrapper
@Validation(0, 10)
def square(a):
return a**2
print(square(2))
# 2022-10-29 09:41:59.377104: 4
print(square(4))
# ValueError: Validation failed
Теперь мы можем передавать границы интервала напрямую в декоратор.
Классами-декораторами можно декорировать как функции, так и другие классы. Они часто встречаются в модулях, и легко отличаются от обычных тем, что их названия записаны в стиле CamelCase.
Заключение
Функторы бывают очень полезны. Они значительно более гибкие, чем функции, поскольку могут содержать дополнительные атрибуты и методы, наследоваться от других классов и иметь наследников. Особо важно то, что функторы могут выступать в роли декораторов, дополняя функциональность функций и классов.
Возможно будет интересно
Как практиковаться в Python?
Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?
match-case: сопоставление структурных шаблонов
Сопоставление структурных шаблонов с помощью match-case во многих случаях проще и предпочтительнее использования условных конструкций. Давайте разберемся, как с ним работать.
Шпаргалка по модулю itertools
Шпаргалка по всем функциям модуля itertools, создающим разнообразные итераторы.