Декораторы Python весьма хороши, однако их достаточно сложно понять при первом знакомстве. Декоратор в Python – это функция, которая принимает другую функцию в качестве аргумента. Декоратор модифицирует или улучшает принятую функцию и выдает измененную. Это значит, что когда вы вызываете декорированную функцию, вы получите функцию, которая может иметь небольшие отличия, в виде дополнительных функций, совмещенных с базовым определением. Нам нужно рассмотреть основу декоратора, а именно функцию.
Простая функция
Python Функция это блок кода, который начинается с ключевого слова def, с дальнейшим названием функции. Функция может принимать от нуля и более аргументов, ключевые аргументы или сочетание этих аргументов. Функция всегда выдает результат. Если вы не определили, что именно она должна выдавать, она выдаст None. Вот очень простой пример функции, которая выдает строку:
1 2 3 4 5 6 7 8 |
# -*- coding: utf-8 -*- def a_function(): """Обычная функция""" return "1+1" if __name__ == "__main__": value = a_function() print(value) |
Все что мы сделали в этом коде, это вызвали функцию и указали значение выдачи. Давайте создадим другую функцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# -*- coding: utf-8 -*- def another_function(func): """ Функция которая принимает другую функцию. """ def other_func(): val = "Результат от %s это %s" % (func(), eval(func()) ) return val return other_func |
Эта функция принимает один аргумент и этот аргумент должен быть функцией или вызываемой. По факту, ее стоит вызывать, используя определенную в прошлом функцию. Вы заметите, что это функция содержит вложенную внутрь функцию, которую мы называем other_func. Она принимает результат переданной функции, её выражение и создает строку, которая говорит нам о том, что мы сделали, после чего она возвращается.
Давайте взглянем на полную версию данного кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# -*- coding: utf-8 -*- def another_function(func): """ Функция которая принимает другую функцию. """ def other_func(): val = "Результат от %s это %s" % (func(), eval(func()) ) return val return other_func def a_function(): """Обычная функция""" return "1+1" if __name__ == "__main__": value = a_function() print(value) decorator = another_function(a_function) print(decorator()) |
Так и работает декоратор. Мы создали одну функцию и передали её другой второй функции. Вторая функция является функцией декоратора. Декоратор модифицирует или усиливает функцию, которая была передана и возвращает модификацию. Если вы запустите этот код, вы увидите следующий выход в stdout:
1 2 |
1+1 Результат от 1+1 это 2 |
Давайте немного изменим код, чтобы превратить another_function в декоратор:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# -*- coding: utf-8 -*- def another_function(func): """ Функция которая принимает другую функцию. """ def other_func(): val = "Результат от %s это %s" % (func(), eval(func()) ) return val return other_func @another_function def a_function(): """Обычная функция""" return "1+1" if __name__ == "__main__": value = a_function() print(value) |
Обратите внимание на то, что декоратор начинается с символа @, за которым следует название функции, которую мы собираемся «декорировать». Для получения декоратора python, вам нужно только разместить его в строке перед определением функции. Теперь, когда мы вызываем **a_function, она будет декорирована, и мы получим следующий результат:
1 |
Результат от 1+1 это 2 |
Давайте создадим декоратор, который будет делать что-нибудь полезное.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Создание логируемого декоратора
Возможно, вам потребуется логировать того, что делает ваша функция. Большую часть времени логинг будет встроен внутри вашей функции. Однако, бывают случаи, когда вам нужно сделать это на уровне функции, что бы получить представление о потоке программы или, возможно, для следования тем или иным условиям бизнеса, таким как аудит. Посмотрим на небольшой декоратор, который мы можем использовать для записи названия любой функции и того, что она делает:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# -*- coding: utf-8 -*- import logging def log(func): """ Логируем какая функция вызывается. """ def wrap_log(*args, **kwargs): name = func.__name__ logger = logging.getLogger(name) logger.setLevel(logging.INFO) # Открываем файл логов для записи. fh = logging.FileHandler("%s.log" % name) fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' formatter = logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) logger.info("Вызов функции: %s" % name) result = func(*args, **kwargs) logger.info("Результат: %s" % result) return func return wrap_log @log def double_function(a): """ Умножаем полученный параметр. """ return a*2 if __name__ == "__main__": value = double_function(2) |
Этот небольшой скрипт содержит функцию log, которая принимает функцию как единственный аргумент. Мы создаем объект логгер, а название лог файла такое же, как и у функции. После этого, функция log будет записывать, как наша функция была вызвана и что она возвращает, если возвращает.
Встроенные декораторы
Python содержит несколько встроенных декораторов. Из всех этих декораторов, самой важной троицей являются:
- @classmethod
- @staticmethod
- @property
Также существуют декораторы в различных разделах стандартной библиотеки Python. Одним из примеров является functools.wraps. Мы сосредоточимся на трех главных декораторах, указанных выше.
@classmethod и @staticmethod
Я не пользовался ими ранее, так что сделал небольшое исследование.
- Декоратор <*@classmethod>* может быть вызван при помощи экземпляра класса, или напрямую, через собственный класс Python в качестве первого аргумента. В соответствии с документацией Python: он может быть вызван как в классе (например, C.f()), или в экземпляре (например, C().f()). Экземпляр игнорируется, за исключением его класса. Если метод класса вызван для выведенного класса, то объект выведенного класса передается в качестве подразумеваемого первого аргумента.
- Декоратор @classmethod, в первую очередь, используется как чередуемый конструктор или вспомогательный метод для инициализации.
- Декоратор <*@staticmethod>* — это просто функция внутри класса. Вы можете вызывать их обоих как с инициализацией класса так и без создания экземпляра класса. Обычно это применяется в тех случаях, когда у вас есть функция, которая, по вашему убеждению, имеет связь с классом. По большей части, это выбор стиля.
Если мы взглянем на пример кода, в котором показано, как работает декоратор, это может помочь понять основные принципы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# -*- coding: utf-8 -*- class DecoratorTest(object): """ Тестируем обычный метод против @classmethod против @staticmethod """ def __init__(self): """Конструктор""" pass def doubler(self, x): print("умножаем на 2") return x*2 @classmethod def class_tripler(klass, x): print("умножаем на 3: %s" % klass) return x*3 @staticmethod def static_quad(x): print("умножаем на 4") return x*4 if __name__ == "__main__": decor = DecoratorTest() print(decor.doubler(5)) print(decor.class_tripler(3)) print(DecoratorTest.class_tripler(3)) print(DecoratorTest.static_quad(2)) print(decor.static_quad(3)) print(decor.doubler) print(decor.class_tripler) print(decor.static_quad) |
Этот пример демонстрирует, что вы можете вызывать обычный метод и оба метода декоратора одним и тем же путем. Обратите внимание на то, что вы можете вызывать обе функции @classmethod и @staticmethod прямо из класса или из экземпляра класса. Если вы попытаетесь вызвать обычную функцию при помощи класса (другими словами, DecoratorTest.doubler(2)), вы получите ошибку TypeError. Также стоит обратить внимание на то, что последний оператор вывода показывает, что decor.static_quad возвращает обычную функцию вместо связанного метода.
Свойства Python (@property)
Python содержит очень удобный небольшой концепт, под названием property, который выполняет несколько полезных задач. Мы рассмотрим, как делать следующее:
- Конвертация метода класс в атрибуты только для чтения;
- Как реализовать сеттеры и геттеры в атрибут
Один из самых простых способов использования property, это использовать его в качестве декоратора метода. Это позволит вам превратить метод класса в атрибут класса. Для меня это было очень полезно, когда мне нужно сделать какую-нибудь комбинацию значений.
Дешевые просмотры Вконтакте с гарантиями Вы найдете на сервисе https://doctorsmm.com/. Помимо приятного прайса, здесь Вы сможете получить персональные условия для работы с полученным ресурсом. Так, например, Вы сможете подобрать скорость поступления страниц таким образом, чтобы она соответствовала статистике Вашего сообщества или профиля. Соответственно, Вы получаете выгодное и действительно безопасное предложение. А еще на сайте постоянно действуют крупные оптовые скидки — торопитесь сделать заказ!
Для других это было очень кстати при написании методов конверсии, которые нужно было принять в качестве методов. Давайте взглянем на простой пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# -*- coding: utf-8 -*- class Person(object): """""" def __init__(self, first_name, last_name): """Конструктор""" self.first_name = first_name self.last_name = last_name @property def full_name(self): """ Возвращаем полное имя """ return "%s %s" % (self.first_name, self.last_name) |
В данном коде мы создали два класса атрибута, или свойств: self.first_name и self.last_name.
Далее мы создали метод full_name, который содержит декоратор <*@property>*. Это позволяет нам использовать следующий код в сессии интерпретатора:
1 2 3 4 5 6 7 8 9 10 |
person = Person("Mike", "Driscoll") print(person.full_name) # Mike Driscoll print(person.first_name) # Mike person.full_name = "Jackalope" Traceback (most recent call last): File "<string>", line 1, in <fragment> AttributeError: can't set attribute |
Как вы видите, в результате превращение метода в свойство, мы можем получить к нему доступ при помощи обычной точечной нотации. Однако, если мы попытаемся настроить свойство на что-то другое, мы получим ошибку AttributeError. Единственный способ изменить свойство full_name, это сделать это косвенно:
1 2 |
person.first_name = "Dan" print(person.full_name) # Dan Driscoll |
Это своего рода ограничение, так что взглянем на другой пример, где мы можем создать свойство, которое позволяет нам делать настройки.
Замена сеттеров и геттеров на свойство Python
Давайте представим, что у нас есть код, который написал кто-то, кто не очень понимает Python. Как и я, вы скорее всего, видели такого рода код ранее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# -*- coding: utf-8 -*- from decimal import Decimal class Fees(object): """""" def __init__(self): """Конструктор""" self._fee = None def get_fee(self): """ Возвращаем текущую комиссию """ return self._fee def set_fee(self, value): """ Устанавливаем размер комиссии """ if isinstance(value, str): self._fee = Decimal(value) elif isinstance(value, Decimal): self._fee = value |
Для использования этого класса, нам нужно использовать сеттеры и геттеры, которые определены как:
1 2 3 4 |
f = Fees() f.set_fee("1") print(f.get_fee()) # Decimal('1') |
Если вам нужно добавить обычную точечную нотацию атрибутов в данный код без выведения из строя всех приложений в этой части кода, вы можете сделать это очень просто, добавив свойство:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# -*- coding: utf-8 -*- from decimal import Decimal class Fees(object): """""" def __init__(self): """Конструктор""" self._fee = None def get_fee(self): """ Возвращаем текущую комиссию """ return self._fee def set_fee(self, value): """ Устанавливаем размер комиссии """ if isinstance(value, str): self._fee = Decimal(value) elif isinstance(value, Decimal): self._fee = value fee = property(get_fee, set_fee) |
Мы добавили одну строк в конце этого кода. Теперь мы можем делать что-то вроде этого:
1 2 3 4 5 6 |
f = Fees() f.set_fee("1") print(f.fee) # Decimal('1') f.fee = "2" print( f.get_fee() ) # Decimal('2') |
Как мы видим, когда мы используем свойство таким образом, это позволяет свойству fee настраивать и получать значение без поломки наследуемого кода. Давайте перепишем этот код с использованием декоратора property, и посмотрим, можем ли мы получить его для разрешения установки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# -*- coding: utf-8 -*- from decimal import Decimal class Fees(object): """""" def __init__(self): """Конструктор""" self._fee = None @property def fee(self): """ Возвращаем текущую комиссию - геттер """ return self._fee @fee.setter def fee(self, value): """ Устанавливаем размер комиссии - сеттер """ if isinstance(value, str): self._fee = Decimal(value) elif isinstance(value, Decimal): self._fee = value if __name__ == "__main__": f = Fees() |
Данный код демонстрирует, как создать сеттер для свойства fee. Вы можете делать это, декорируя второй метод, который также называется fee с декоратором, под названием <@fee.setter>. Сеттер будет вызван, когда вы сделаете что-то вроде следующего:
1 2 |
f = Fees() f.fee = "1" |
Если вы взгляните на подписи под свойством, то это будут fget, fset, fdel и doc в качестве аргументов. Вы можете создать другой декорируемый метод, используя то же название связи с функцией delet при помощи <@fee.deleter*>*, если вы хотите поймать команду **del для атрибута.
Подведем итоги
С этого момента вы должны понимать, как создавать собственные декораторы и как использовать встроенные декораторы Python. Мы рассмотрели classmethod, @property и @staticmethod. Надеюсь, вы будете использовать встроенные декораторы, и создавать свои собственные.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»