MyTetra Share
Делитесь знаниями!
Изучаем Декораторы в Python
29.07.2019
23:51
Текстовые метки: Python, python-scripts.com, decorators
Раздел: !Закладки - Python - python-scripts.com

python-scripts.com

Изучаем Декораторы в Python

автор

13-19 минут


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

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

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


# -*- coding: utf-8 -*-

def a_function():

    """Обычная функция"""

    return "1+1"

if __name__ == "__main__":

    value = a_function()

    print(value)

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


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

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, это использовать его в качестве декоратора метода. Это позволит вам превратить метод класса в атрибут класса. Для меня это было очень полезно, когда мне нужно сделать какую-нибудь комбинацию значений. Для других это было очень кстати при написании методов конверсии, которые нужно было принять в качестве методов. Давайте взглянем на простой пример:


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


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


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

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


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)

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


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

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

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

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


Так же в этом разделе:
 
MyTetra Share v.0.52
Яндекс индекс цитирования