Хочешь писать лаконичный, читаемый и эффективный код? Тогда декораторы помогут тебе в этом.
В седьмой главe «Fluent Python» Лучано Ромальо рассказывает о декораторах и замыкании. Они не очень распространены в Data Science, однако как только вы начинаете проектировать модели и писать ассинхронный код, декораторы становятся бесценными помощниками.
1 — Что такое декораторы?
Перед тем как мы перейдем к советам, давайте рассмотрим работу декораторов.
Декораторы — это простые функции, которые принимают на вход функцию. Чаще всего они изображаются как "@my_decorator"
над декорируемой функцией.
temp = 0 def decorator_1(func): print('Running our function') return func @decorator_1 def temperature(): return temp print(temperature())
Однако то, как мы вызываем функцию temperature()
может сбить с толку. Нужно просто использоватьdecorator_1(temperature())
,как в примере ниже:
temp = 0 def decorator_1(func): print('Running our function') return func def temperature(): return temp decorator_1(temperature())
Хорошо, значит декораторы — это функции, которые принимают другую функцию в качестве аргументы. Но зачем нам вообще нужно когда-либо их использовать?
Декораторы, действительно, универсальные и мощные инструменты. Они широко применяются для асинронных функций обратного вызова и функционального программирования. Декораторы могут также использоваться для превращения class-like функциональности в непосредственные функции, сокращая при этом время разработки и заполнение памяти.
2 — Декоратор свойств
Совет
Используйте встроенный декоратор@property
для расширения функциональности геттеров и сеттеров.
Одним из самых используемых встроенных декораторов является @property
. Множетсво ООП языков(Java, С++) предоставляют возможность использовать геттеры и сеттеры. Данные функции используются с целью гарантировать, что наша переменная не вернет/установит некорректное значение.Одним из примеров может служить наша переменная temp, которая по услови. должна быть больше нуля.
class my_vars: def __init__(self, t): self._temp = t def my_getter(self): return self._temp def my_setter(self, t): if t > −273.15: self._temp = t else: print('Below absolute 0!') v = my_vars(500) print(v.my_getter()) # 500 v.my_setter(-1000) # 'Below absolute 0!' v.my_setter(-270) print(v.my_getter()) # -270
Мы можем расширить функциональность многих вещей, используя @property
, делая при этом код чище и динамичнее:
class my_vars: def __init__(self, t): self._temp = t @property def temperature(self): return self._temp @temperature.setter def temperature(self, t): self._temp = t c = my_vars(500) print(c.temperature) # 500 c.temperature = 1 print(c.temperature) # 1
Заметьте, что мы удалили все условные операторы из my_setter() для краткости, но смысл остался тот же.
Перед тем как мы двинемся дальше, есть еще одно уточнение. В python не существует такого понятия, как «приватные переменные». Префикс «_» указывает на то, что переменная защищена и на нее не стоит ссылаться вне класса. Однако вы все еще можете сделать так:
c = my_vars(500) print(c._temp) # 500 c._temp = -10000 print(c._temp) # -1000
Отсутствие приватных переменных в Python являлось интересной дизайнерской задумкой. Аргументы — это приватные переменная в ООП, которые на самом деле не является таковыми: если кто-то захочет получить к ним доступ, то он может изменить источник кода класса и сделать переменную публичной.
Python поощряет «ответственную разработку» и позволяет вам получить извне доступ ко всему в классе.
3 — Статические методы и методы классов
Совет
Используйте@classmethodи @staticmethodдля расширения функциональности классов
Эти два декоратора многих сбивают с толку, но их отличия налицо:
-
@classmethod
принимает класс в качестве параметра. По этой причине методы классов могут модифицировать сам класс через все его экземпляры. -
@staticmethod
принимает экземпляр класса. По этой причине статические методы вовсе не могут модифицировать классы.
Обратимся к примеру:
class Person: def __init__(self, name, age): self.name = name self.age = age @classmethod def fromBirthYear(cls, name, year): return cls(name, date.today().year - year) @staticmethod def isAdult(age): return age > 18
Самый важный фактор, определяющий значимость методов класса, это их способность служить альтернативным конструктором для наших классов, которые действительно полезны для полиморфизма. Даже если вы не делайте всякие безумные вещи с наследованием, то все еще прекрасно иметь возможность конкретизировать различные версии классов без использования if/else.
С другой стороны, статические методы чаще всего используются в качестве вспомогательных функций, которые абсолютно независимы от состояния класса. Заметьте, что функция isAdult(age) не требует привычного self аргумента, так что она не может сослаться на класс, даже если очень захочется.
4 — Быстрый совет
Совет
Используйте @functools.wraps
, чтобы хранить информацию функции.
Запомните, декораторы — это просто функции, которые принимают другие функции. Так что, когда мы вызываем «декорированные» функции, в первую очередь мы вызываем сам декоратор.Этот поток перезаписывает информацию о «декорированной» функции, например, __name__
и __doc__
поля.
Чтобы решить эту проблему, мы можем обратиться к следующему декоратору:
from functools import wraps def my_decorator(func): @wraps(func) def call_func(*args): return func(*args) return call_func @my_decorator def f(x): """does some math""" return x + x * x print(f(5)) # 30 print(f.__name__) # 'f' print(f.__doc__) # 'does some math'
Без декоратора @wraps результат напечатанного утверждения будет следующим:
print(f(5)) # 30 print(f.__name__) # 'call_func' print(f.__doc__) # '
Чтобы избежать переписывания важной информации, убедитесь в использовании @functools.wraps
5 — Создавайте пользовательские декораторы
Совет
Пишите свои собственные декораторы, чтобы улучшить свой рабочий процесс, но будьте осторожны!
Область видимости в декораторах немного странная. У нас нет времени на детали, но есть эта <a href=»https://towardsdatascience.com/closures-and-decorators-in-python-2551abbc6eb6«>статья</a>. Примите во внимание, что если вы получаете эту ошибку, то вам следует почитать про область видимости декораторов:
Перейдем к некоторым пользовательским декораторам.
5.1 — Сохраняем функции, основанные на декораторах
Код ниже добавляет функции в список при вызове
# Desc: store all ml models and call them ml_models = [] def ml(func): ml_models.append(func) def call_func(*args, **kwargs): return func(*args, **kwargs) return call_func @ml def CNN(): print('Convolutional Neural Net') @ml def RNN(): print('Recurrent Neural Net') def linear_regression(): print('This isn't ML') # call all ML models for m in ml_models: m() print(ml_models) # returns list of functions for reference
Потенциальный пример использования — юнит-тестирование, так же как с pytest. Условно, мы имеем быстрые и медленные тесты. Вместо того, чтобы вручную назначать каждый отдельному списку, мы можем просто добавить@slowили @fastдекораторы для каждой функции, а затем вызвать каждое значение в соответсвующем списке.
5.2 — Запросы временных данных и модельное обучение
Код ниже выводит время исполнения вашей функции
# Desc: create a decorator that prints start/end time of function import numpy as np def time_it(func): def timer(*args): start = np.datetime64('now') print(start) result = func(*args) end = np.datetime64('now') print(end) print(f'{(end - start) / np.timedelta64(1, "s")} secs') return result return timer @time_it def long_function(x): for i in range(x): _ = i * i + 5 long_function(int(1e6)) """ Output: 2022-01-24T02:37:15 2.0 secs 2022-01-24T02:39:15 """
Если вы запускаете любой тип запроса данных или обучаете модель с плохими логами, полезно иметь оценку время исполнения программы. Вы можете оценить время исполнения любой функции, пользуясь декоратором @time_it
.
5.3 — Выполнять управление потоком на входе функции
Приведенный ниже код выполняет условные проверки параметров функции перед выполнением самой функции.
def check_not_None(func): def check(x): if x is not None: return func(x) else: return 'is None' return check @check_not_None def f1(x): return x**1 @check_not_None def f2(x): return x**2 @check_not_None def f3(x): return x**3 print(f1(4)) # 4 print(f2(None)) # 'is None' print(f3(4)) # 64
Этот декоратор применяет логику условий на все параметры x функции. Без декоратора нам бы пришлось писать if is not None
для каждой функции.
И это всего лишь несколько примеров. Декораторы действительно могут быть очень полезными!
ссылка на оригинал статьи https://habr.com/ru/post/648967/
Добавить комментарий