GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Python 3
  • Скачать Python
  • IDE
  • Скачать PyCharm
  • Django
  • Tkinter
  • Книги

This view

  • Original page
  • New address

GeezerVison is showing python-scripts.com — Изучаем Декораторы в Python

Декораторы Python весьма хороши, однако их достаточно сложно понять при первом знакомстве. Декоратор в Python – это функция, которая принимает другую функцию в качестве аргумента. Декоратор модифицирует или улучшает принятую функцию и выдает измененную. Это значит, что когда вы вызываете декорированную функцию, вы получите функцию, которая может иметь небольшие отличия, в виде дополнительных функций, совмещенных с базовым определением. Нам нужно рассмотреть основу декоратора, а именно функцию.

Простая функция

Python Функция это блок кода, который начинается с ключевого слова def, с дальнейшим названием функции. Функция может принимать от нуля и более аргументов, ключевые аргументы или сочетание этих аргументов. Функция всегда выдает результат. Если вы не определили, что именно она должна выдавать, она выдаст None. Вот очень простой пример функции, которая выдает строку:

Python
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)

Все что мы сделали в этом коде, это вызвали функцию и указали значение выдачи. Давайте создадим другую функцию:

Python
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. Она принимает результат переданной функции, её выражение и создает строку, которая говорит нам о том, что мы сделали, после чего она возвращается.

Давайте взглянем на полную версию данного кода:

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
# -*- 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:

Python
1
2
1+1
Результат от 1+1 это 2

Давайте немного изменим код, чтобы превратить another_function в декоратор:

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
# -*- 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, она будет декорирована, и мы получим следующий результат:

Python
1
Результат от 1+1 это 2

Давайте создадим декоратор, который будет делать что-нибудь полезное.

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Python Форум Помощи

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Чат
Канал

Паблик VK

Одно из самых больших сообществ по 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
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>* — это просто функция внутри класса. Вы можете вызывать их обоих как с инициализацией класса так и без создания экземпляра класса. Обычно это применяется в тех случаях, когда у вас есть функция, которая, по вашему убеждению, имеет связь с классом. По большей части, это выбор стиля.

Если мы взглянем на пример кода, в котором показано, как работает декоратор, это может помочь понять основные принципы:

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
# -*- 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/. Помимо приятного прайса, здесь Вы сможете получить персональные условия для работы с полученным ресурсом. Так, например, Вы сможете подобрать скорость поступления страниц таким образом, чтобы она соответствовала статистике Вашего сообщества или профиля. Соответственно, Вы получаете выгодное и действительно безопасное предложение. А еще на сайте постоянно действуют крупные оптовые скидки — торопитесь сделать заказ!

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

Python
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>*. Это позволяет нам использовать следующий код в сессии интерпретатора:

Python
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, это сделать это косвенно:

Python
1
2
person.first_name = "Dan"
print(person.full_name) # Dan Driscoll

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

Замена сеттеров и геттеров на свойство Python

Давайте представим, что у нас есть код, который написал кто-то, кто не очень понимает 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

Для использования этого класса, нам нужно использовать сеттеры и геттеры, которые определены как:

Python
1
2
3
4
f = Fees()
f.set_fee("1")
 
print(f.get_fee()) # Decimal('1')

Если вам нужно добавить обычную точечную нотацию атрибутов в данный код без выведения из строя всех приложений в этой части кода, вы можете сделать это очень просто, добавив свойство:

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
# -*- 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)

Мы добавили одну строк в конце этого кода. Теперь мы можем делать что-то вроде этого:

Python
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, и посмотрим, можем ли мы получить его для разрешения установки.

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
# -*- 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>. Сеттер будет вызван, когда вы сделаете что-то вроде следующего:

Python
1
2
f = Fees()
f.fee = "1"

Если вы взгляните на подписи под свойством, то это будут fget, fset, fdel и doc в качестве аргументов. Вы можете создать другой декорируемый метод, используя то же название связи с функцией delet при помощи <@fee.deleter*>*, если вы хотите поймать команду **del для атрибута.

Подведем итоги

С этого момента вы должны понимать, как создавать собственные декораторы и как использовать встроенные декораторы Python. Мы рассмотрели classmethod, @property и @staticmethod. Надеюсь, вы будете использовать встроенные декораторы, и создавать свои собственные.

Vasile Buldumac

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: [email protected]

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.