Разное

Декоратор python: Python 3 для начинающих и чайников

Содержание

Понимаем декораторы в Python’e, шаг за шагом. Шаг 1 / Хабр

На Хабре множество раз обсуждалась тема декораторов, однако, на мой взгляд, данная статья (выросшая из одного вопроса на stackoverflow) описывает данную тему наиболее понятно и, что немаловажно, является «пошаговым руководством» по использованию декораторов, позволяющим новичку овладеть этой техникой сразу на достойном уровне.

Итак, что же такое «декоратор»?

Впереди достаточно длинная статья, так что, если кто-то спешит — вот пример того, как работают декораторы:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped
 
def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped
 
@makebold
@makeitalic
def hello():
    return "hello habr"
 
print hello() ## выведет <b><i>hello habr</i></b>

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

Функции в Python’e являются объектами

Для того, чтобы понять, как работают декораторы, в первую очередь следует осознать, что в Python’е функции — это тоже объекты.
Давайте посмотрим, что из этого следует:

def shout(word="да"):
    return word.capitalize()+"!"
 
print shout()
# выведет: 'Да!'
 
# Так как функция - это объект, вы связать её с переменнной,
# как и любой другой объект
scream = shout
 
# Заметьте, что мы не используем скобок: мы НЕ вызываем функцию "shout",
# мы связываем её с переменной "scream". Это означает, что теперь мы
# можем вызывать "shout" через "scream":
 
print scream()
# выведет: 'Да!'

# Более того, это значит, что мы можем удалить "shout", и функция всё ещё
# будет доступна через переменную "scream"
 
del shout
try:
    print shout()
except NameError, e:
    print e
    #выведет: "name 'shout' is not defined"
 
print scream()
# выведет: 'Да!'

Запомним этот факт, скоро мы к нему вернёмся, но кроме того, стоит понимать, что функция в Python’e может быть определена… внутри другой функции!

def talk():
    # Внутри определения функции "talk" мы можем определить другую...
    def whisper(word="да"):
        return word.lower()+"...";
 
    # ... и сразу же её использовать!
    print whisper()

# Теперь, КАЖДЫЙ РАЗ при вызове "talk", внутри неё определяется а затем
# и вызывается функция "whisper".
talk()
# выведет: "да..."
 
# Но вне функции "talk" НЕ существует никакой функции "whisper":
try:
    print whisper()
except NameError, e:
    print e
    #выведет : "name 'whisper' is not defined"

Ссылки на функции

Ну что, вы всё ещё здесь?:)

Теперь мы знаем, что функции являются полноправными объектами, а значит:

  • могут быть связаны с переменной;
  • могут быть определены одна внутри другой.

Что ж, а это значит, что одна функция может вернуть другую функцию!
Давайте посмотрим:

def getTalk(type="shout"):
 
    # Мы определяем функции прямо здесь
    def shout(word="да"):
        return word.capitalize()+"!"
 
    def whisper(word="да") :
        return word.lower()+"...";
 
    # Затем возвращаем необходимую
    if type == "shout":
        # Заметьте, что мы НЕ используем "()", нам нужно не вызвать функцию,
        # а вернуть объект функции
        return shout
    else:
        return whisper
 
# Как использовать это непонятное нечто?
# Возьмём функцию и свяжем её с переменной
talk = getTalk()
 
# Как мы можем видеть, "talk" теперь - объект "function":
print talk
# выведет: <function shout at 0xb7ea817c>
 
# Который можно вызывать, как и функцию, определённую "обычным образом":
print talk()
 
# Если нам захочется - можно вызвать её напрямую из возвращаемого значения:
print getTalk("whisper")()
# выведет: да...

Подождите, раз мы можем возвращать функцию, значит, мы можем и передавать её другой функции, как параметр:

def doSomethingBefore(func):
    print "Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал"
    print func()
 
doSomethingBefore(scream)
#выведет:
# Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал
# Да!

Ну что, теперь у нас есть все необходимые знания для того, чтобы понять, как работают декораторы.
Как вы могли догадаться, декораторы — это, по сути, просто своеобразные «обёртки», которые дают нам возможность делать что-либо до и после того, что сделает декорируемая функция, не изменяя её.

Создадим свой декоратор «вручную»
# Декоратор - это функция, ожидающая ДРУГУЮ функцию в качестве параметра
def my_shiny_new_decorator(a_function_to_decorate):
    # Внутри себя декоратор определяет функцию-"обёртку".
    # Она будет (что бы вы думали?..) обёрнута вокруг декорируемой,
    # получая возможность исполнять произвольный код до и после неё.

    def the_wrapper_around_the_original_function():
        # Поместим здесь код, который мы хотим запускать ДО вызова
        # оригинальной функции
        print "Я - код, который отработает до вызова функции"
 
        # ВЫЗОВЕМ саму декорируемую функцию
        a_function_to_decorate()

        # А здесь поместим код, который мы хотим запускать ПОСЛЕ вызова
        # оригинальной функции
        print "А я - код, срабатывающий после"

    # На данный момент функция "a_function_to_decorate" НЕ ВЫЗЫВАЛАСЬ НИ РАЗУ

    # Теперь, вернём функцию-обёртку, которая содержит в себе
    # декорируемую функцию, и код, который необходимо выполнить до и после.
    # Всё просто!
    return the_wrapper_around_the_original_function

# Представим теперь, что у нас есть функция, которую мы не планируем больше трогать.
def a_stand_alone_function():
    print "Я простая одинокая функция, ты ведь не посмеешь меня изменять?.."
 
a_stand_alone_function()
# выведет: Я простая одинокая функция, ты ведь не посмеешь меня изменять?..
 
# Однако, чтобы изменить её поведение, мы можем декорировать её, то есть
# Просто передать декоратору, который обернет исходную функцию в любой код,
# который нам потребуется, и вернёт новую, готовую к использованию функцию:
 
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#выведет:
# Я - код, который отработает до вызова функции
# Я простая одинокая функция, ты ведь не посмеешь меня изменять?..
# А я - код, срабатывающий после

Наверное, теперь мы бы хотели, чтобы каждый раз, во время вызова a_stand_alone_function, вместо неё вызывалась a_stand_alone_function_decorated. Нет ничего проще, просто перезапишем a_stand_alone_function функцией, которую нам вернул my_shiny_new_decorator:

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#выведет:
# Я - код, который отработает до вызова функции
# Я простая одинокая функция, ты ведь не посмеешь меня изменять?..
# А я - код, срабатывающий после

Вы ведь уже догадались, что это ровно тоже самое, что делают @декораторы.:)

Разрушаем ореол таинственности вокруг декораторов

Вот так можно было записать предыдущий пример, используя синтаксис декораторов:

@my_shiny_new_decorator
def another_stand_alone_function():
    print "Оставь меня в покое"
 
another_stand_alone_function()
#выведет:
# Я - код, который отработает до вызова функции
# Оставь меня в покое
# А я - код, срабатывающий после

Да, всё действительно так просто! decorator — просто синтаксический сахар для конструкций вида:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

Декораторы — это просто pythonic-реализация паттерна проектирования «Декоратор». В Python включены некоторые классические паттерны проектирования, такие как рассматриваемые в этой статье декораторы, или привычные любому пайтонисту итераторы.

Конечно, можно вкладывать декораторы друг в друга, например так:

def bread(func):
    def wrapper():
        print "</------\>"
        func()
        print "<\______/>"
    return wrapper
 
def ingredients(func):
    def wrapper():
        print "#помидоры#"
        func()
        print "~салат~"
    return wrapper
 
def sandwich(food="--ветчина--"):
    print food
 
sandwich()
#выведет: --ветчина--
sandwich = bread(ingredients(sandwich))
sandwich()
#выведет:
# </------\>
# #помидоры#
# --ветчина--
# ~салат~
# <\______/>

И используя синтаксис декораторов:

@bread
@ingredients
def sandwich(food="--ветчина--"):
    print food
 
sandwich()
#выведет:
# </------\>
# #помидоры#
# --ветчина--
# ~салат~
# <\______/>

Следует помнить о том, что порядок декорирования ВАЖЕН:

@ingredients
@bread
def sandwich(food="--ветчина--"):
    print food
 
sandwich()
#выведет:
# #помидоры#
# </------\>
# --ветчина--
# <\______/>
# ~салат~

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

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

Содержание:

Прим. переводчика:

Спасибо за внимание. Буду благодарен любым замечаниям по переводу и оформлению, постараюсь учесть их все во второй части перевода.

UPD: Во второй части будут разобраны такие вопросы, как: передача аргументов декорируемым функциям, декорация методов, декораторы с параметрами и пр.
UPD2: Выложена вторая часть статьи.

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

Декораторы 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 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



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

def another_function(func):

    «»»

    Функция которая принимает другую функцию.

    «»»

    

    def other_func():

        val = «Результат от %s это %s» % (func(),

            eval(func())

        )

        return val

    

    return other_func

Эта функция принимает один аргумент и этот аргумент должен быть функцией или вызываемой. По факту, ее стоит вызывать, используя определенную в прошлом функцию. Вы заметите, что это функция содержит вложенную внутрь функцию, которую мы называем other_func. Она принимает результат переданной функции, её выражение и создает строку, которая говорит нам о том, что мы сделали, после чего она возвращается.

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

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


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



1+1

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

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

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


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 это 2



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

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

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

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

Telegram Чат & Канал

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

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!


Создание логируемого декоратора

Возможно, вам потребуется логировать того, что делает ваша функция. Большую часть времени логинг будет встроен внутри вашей функции. Однако, бывают случаи, когда вам нужно сделать это на уровне функции, что бы получить представление о потоке программы или, возможно, для следования тем или иным условиям бизнеса, таким как аудит. Посмотрим на небольшой декоратор, который мы можем использовать для записи названия любой функции и того, что она делает:

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


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

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

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


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

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

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



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



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



person.first_name = «Dan»

print(person.full_name) # Dan Driscoll

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

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

Давайте представим, что у нас есть код, который написал кто-то, кто не очень понимает Python. Как и я, вы скорее всего, видели такого рода код ранее:

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

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



f = Fees()

f.set_fee(«1»)

 

print(f.get_fee()) # Decimal(‘1’)

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

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

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



f = Fees()

f.set_fee(«1»)

print(f.fee) # Decimal(‘1’)

 

f.fee = «2»

print( f.get_fee() ) # Decimal(‘2’)

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

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


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

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

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

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

Python. Урок 19. Декораторы функций в Python

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

Что нужно знать о функциях в Python?

Для начала разберем два аспекта связанные с функциями в Python. Во-первых: функция – это объект специального вида, поэтому ее можно передавать в качестве аргумента другим функциям. Во-вторых: внутри функций можно создавать другие функции, вызывать их и возвращать как результат через return. Остановимся на этих моментах более подробно.

Функция как объект

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

>>> # исходный список
>>> a = [1, 2, 3, 4, 5]
>>> # функция, возводящая переданное ей число в квадрат
>>> sq = lambda x: x**2
>>> # проверим ее работу
>>> print(sq(5))
25

>>> # получаем список квадратов
>>> b = list(map(sq, a))
>>> print(b)
[1, 4, 9, 16, 25]

Здесь мы передали функции map в качестве первого аргумента функцию sq, которая будет применяться по очереди ко всем элементам списка a.

В Python функция – это специальный объект, который имеет метод __call__(). Если мы создадим вот такой класс.

class DemoCall():
    def __call__(self):
        return "Hello!"

То объект такого класса можно вызывать как функцию.

>>> hello = DemoCall()
>>> hello()
'Hello!'

Функция внутри функции

Вторым важным свойством функции, для понимания темы декораторов, является то, что их можно создавать, вызывать и возвращать из других функций. На этом построена идея замыкания (closures).

Например, создадим функцию, которая умножает два числа.

def mul(a):
    def helper(b):
        return a * b
    return helper

В ней реализованы два важных свойства которые нам понадобятся: внутри функции mul() создается еще одна функция, которая называется helper(); функция mul() возвращает функцию helper как результат работы.

Вызывается эта функция так:

>>>mul(4)(2)
8

Ее главная фишка состоит в том, что можно создавать на базе функции mul() свои кастомизированные функции. Например, создадим функцию “умножение на три”.

>>>three_mul = mul(3)
>>>three_mul(5)
15

Как вы можете видеть, мы построили функцию three_mul, которая умножает на три любое переданное ей число.

Что такое декоратор функции в Python?

Конструктивно декоратор в Python представляет собой некоторую функцию, аргументом которой является другая функция. Декоратор предназначен для добавления дополнительного функционала к данной функции без изменения содержимого последней.

Создание декоратора

Предположим у нас есть пара простых функций, вот они:

def first_test():
    print("Test function 1")

def second_test():
    print("Test function 2")

Мы хотим дополнить их так, чтобы перед вызовом основного кода функции печаталась строка “Run function”, а по окончании – “Stop function”.

Сделать это можно двумя способами. Первый – это добавить указанные строки в начало в конец каждой функции, но это не очень удобно, т.к. если мы захотим убрать это, нам придется снова модифицировать тело функции. А если они написаны не нами, либо являются частью общей кодовой базы проекта, сделать это будет уже не так просто. Второй вариант – это воспользоваться знаниями из раздела “Что нужно знать о функциях в Python?”

Создадим вот такую функцию.

def simple_decore(fn):
    def wrapper():
        print("Run function")
        fn()
        print("Stop function")
    return wrapper

Обернем наши функции в эту оболочку.

first_test_wrapped = simple_decore(first_test)
second_test_wrapped = simple_decore(second_test)

Функции first_test и second_test остались неизменными.

>>> first_test()
Test function 1
>>> second_test()
Test function 2

Функции first_test_wrapped и second_test_wrapped  обладают функционалом, которого мы добивались.

>>> first_test_wrapped()
Run function
Test function 1
Stop function
>>> first_test_wrapped()
Run function
Test function 1
Stop function

Если необходимо, чтобы так работали функций с именами first_test и second_test, то  можно сделать так.

first_test = first_test_wrapped
second_test = second_test_wrapped

Проверим это.

>>> first_test()
Run function
Test function 1
Stop function
>>> second_test()
Run function
Test function 2
Stop function

То, что мы только что сделали и является реализацией идеи декоратора. Но вместо строк:

def first_test():
    print("Test function 1")
first_test_wrapped = simple_decore(first_test)
first_test = first_test_wrapped 

Можно написать вот так:

@simple_decore
def first_test():
    print("Test function 1")

@simple_decore – это и есть декоратор функции.

Передача аргументов в функцию через декоратор

Если функция в своей работе требует наличие аргумента, то его можно передать через декоратор. Создадим декоратор, который принимает аргумент и выводит информацию о декорируемой функции и ее аргументе.

def param_transfer(fn):
   def wrapper(arg):
       print("Run function: " + str(fn.__name__) + "(), with param: " + str(arg))
       fn(arg)
   return wrapper

Для демонстрации ее работы создадим функцию, которая выводит квадратный корень переданного ей числа, в качестве декоратора, укажем только что созданный param_transfer.

@param_transfer
def print_sqrt(num):
   print(num**0.5)

Выполним эту функцию с аргументом 4.

>>> print_sqrt(4)
Run function: print_sqrt(), with param: 4
2.0

Декораторы для методов класса

Методы классов также можно объявлять с декоратором. Модифицируем декоратор param_transfer.

def method_decor(fn):
   def wrapper(self):
       print("Run method: " + str(fn.__name__))
       fn(self)
   return wrapper

Создадим класс для представления двумерного вектора (из математики). В этом классе определим метод norm(), который выводит модуль вектора.

class Vector():
   def __init__(self, px = 0, py = 0):
       self.px = px
       self.py = py

   @method_decor
   def norm(self):
       print((self.px**2 + self.py**2)**0.5)

Продемонстрируем работу этого метода.

>>> vc = Vector(px=10, py=5)
>>> vc.norm()
Run method: norm
11.180339887498949

Возврат результата работы функции через декоратор

Довольно часто, создаваемые функции возвращают какое-либо значение. Для того, чтобы его можно было возвращать через декоратор необходимо соответствующим образом построить внутреннюю функцию.

def decor_with_return(fn):
   def wrapper(*args, **kwargs):
       print("Run method: " + str(fn.__name__))
       return fn(*args, **kwargs)
   return wrapper

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

@decor_with_return
def calc_sqrt(val):
   return val**0.5

Выполним функцию calc_sqrt с параметром 16.

>>> tmp = calc_sqrt(16)
Run method: calc_sqrt
>>> print(tmp)
4.0

P.S.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

<<<Python. Урок 18. Аннотация типов в Python    Python. Урок 20. Объектная модель в Python>>>

Декораторы в Python 3 и их применение| ООП

Декораторы принимают функцию, добавляют в нее некоторую функциональность и возвращают ее обратно. Из данного пособия вы узнаете, как создавать декораторы и зачем это нужно делать.

Декораторы на Python

Для придания новой функциональности уже существующему коду в Python есть очень интересный инструмент под названием декоратор (decorator).

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

Необходимые условия для понимания данного материала

Чтобы понять что такое декораторы, вы должны знать несколько базовых вещей в Python.

В первую очередь вас не должно удивлять, что в Python все (и даже классы!) является объектами. Задаваемые нами имена — это просто идентификаторы, привязанные к конкретным объектам. Функции также не являются исключением, это такие же объекты со своими атрибутами. К одной и той же функции может быть привязано несколько совершенно разных имен.

Вот пример:

def first(msg):
    print(msg)


first("Hello")

second = first
second("Hello")

Результат:

Hello
Hello

При работе данного кода обе функции, first и second, дают один и тот же результат. Это оттого, что имена first и second относятся к одному объекту.

Теперь сгустим немного краски.

Функции могут быть переданы в качестве аргументов в другие функции!

Если вы в Python пользовались функциями типа mapfilter или reduce, то уже об этом знаете.

Такие функции в Python называются функциями высшего порядка. Вот пример такой функции:

def inc(x):
    return x + 1


def dec(x):
    return x - 1


def operate(func, x):
    result = func(x)
    return result

Мы вызываем такую функцию следующим образом:

>>> operate(inc,3)
4
>>> operate(dec,3)
2

Более того, функция может возвращать другую функцию.

def is_called():
    def is_returned():
        print("Hello")
    return is_returned


new = is_called()

# Результат "Hello"
new()

Результат:

Hello

Здесь is_returned() — это вложенная функция, так как она определена внутри функции is_called(). И она возвращается каждый раз, как только мы вызываем is_called().

И еще, мы должны знать, как работают замыкания в Python.

Возвращаемся обратно к декораторам

Функции и методы в Python являются вызываемыми объектами, поскольку могут быть вызваны.

Фактически, любой объект в Python, для которого реализован специальный метод __call__(), является вызываемым. Таким образом, в наиболее общем смысле декоратор является вызываемым объектом и также возвращает вызываемый объект.

В общем и целом, декоратор принимает функцию, добавляет в нее некоторую функциональность и возвращает её.

def make_pretty(func):
    def inner():
        print("I got decorated")
        func()
    return inner


def ordinary():
    print("I am ordinary")

Когда вы запускаете данный код в интерпретаторе, происходит следующее:

>>> ordinary()
I am ordinary

>>> # Давайте декорируем функцию ordinary()
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary

В данном примере функция make_pretty() является декоратором. Декорирование происходит вот на этом шаге:

pretty = make_pretty(ordinary)

Функция ordinary() теперь задекорирована и возвращаемая функция носит имя pretty().

Мы видим, что декоратор добавил некоторую новую функциональность в первоначальную функцию. Это напоминает упаковку для подарка. Декоратор играет роль такой упаковки. Суть самого объекта в результате декорирования не меняется. Но теперь этот объект выглядит симпатичнее (что не удивительно, он ведь был декорирован).

Обычно мы декорируем и переопределяем функцию следующим образом:

pretty = make_pretty(ordinary)

Это довольно частая конструкция, и поэтому в Python есть синтаксис для ее упрощения.

Мы используем символ @ перед названием функции и помещаем его прямо над объявлением функции, которая должна быть задекорирована. Например:

@make_pretty
def ordinary():
    print("I am ordinary")

Это эквивалентно следующему коду:

def ordinary():
    print("I am ordinary")
ordinary = make_pretty(ordinary)

Такой вот синтаксический сахар для реализации декораторов.

Декорирование функций с параметрами

Разобранный нами декоратор был очень простым и мог работать только с функциями без параметров. А как быть, если у функции есть параметры? Например, вот такие:

def divide(a, b):
    return a/b

У этой функции есть два параметра, a и b и мы также знаем, что если b будет равно 0, возникнет ошибка.

>>> divide(2, 5)
0.4
>>> divide(2, 0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

Теперь давайте сделаем декоратор, который будет проверять наличие нуля и предотвращать ошибку.

def smart_divide(func):
    def inner(a, b):
        print("Я собираюсь разделить", a, "на", b)
        if b == 0:
            print("Упс! Не могу выполнить деление")
            return

        return func(a, b)
    return inner


@smart_divide
def divide(a, b):
    print(a/b)

В новой реализации функция вернет None, если возникнет ситуация с делением на 0:

>>> divide(2,5)
Я собираюсь разделить 2 на 5
0.4

>>> divide(2,0)
Я собираюсь разделить 2 на 0
Упс! Не могу выполнить деление

Таким образом мы можем декорировать функции, имеющие параметры.

Внимательный читатель может заметить, что вложенная внутри декоратора функция inner() имеет те же параметры, что и функция, которую мы декорируем. Приняв это во внимание, мы теперь можем построить декоратор для функции с произвольным количеством параметров.

В Python эта магия выглядит следующим образом: function(*args, **kwargs). Здесь *args — это кортеж позиционных аргументов, а **kwargs — словарь ключевых аргументов. Вот пример такого декоратора:

def works_for_all(func):
    def inner(*args, **kwargs):
        print("Я могу декорировать любую функцию")
        return func(*args, **kwargs)
    return inner

Создание цепочек декораторов

В Python может последовательно применяться сразу несколько декораторов.

Иными словами, функция может быть декорирована несколько раз разными (или одними и теми же!) декораторами. Мы просто помещаем эти декораторы поверх функции, которую хотим задекорировать.

def star(func):
    def inner(*args, **kwargs):
        print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner


def percent(func):
    def inner(*args, **kwargs):
        print("%" * 30)
        func(*args, **kwargs)
        print("%" * 30)
    return inner


@star
@percent
def printer(msg):
    print(msg)


printer("Hello")

Результат:

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

Данный синтаксис

@star
@percent
def printer(msg):
    print(msg)

эквивалентен следующему:

def printer(msg):
    print(msg)
printer = star(percent(printer))

Порядок, в котором мы применяем декораторы, имеет значение. Например, изменив порядок следующим образом:

@percent
@star
def printer(msg):
    print(msg)

Мы получим вот такой результат:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

работа с функциями и классами, способы применения

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

  1. Введение

В Python все является объектом. И функции — не исключение. Поскольку они тоже являются объектами, их можно передавать в качестве аргументов другим функциям и возвращать в качестве результата. У них также есть атрибуты: __name__ и __doc__.

Функция — это группа инструкций, которые выполняют определенную задачу. Задача функции — организовать код, избежать повторений и заново использовать отдельные блоки.

def function_name(args):
    '''docstring'''
    statements(s)

Синтаксис функций очень простой. Она начинается с ключевого слова def. Дальше идет уникальное имя, параметры и двоеточие. Также может быть документация (docstring), которая описывает назначение функции.

Все инструкции, которые идут дальше, составляют тело функции. Они должны иметь корректные отступы. В конце также может быть инструкция return.

					

def basic_function(a):
'''Базовая функция'''
print('Базовая функция:', a)
return a + 1

print('старт программы')
print('имя:', basic_function.__name__)
print('док:', basic_function.__doc__)
b = basic_function(1)
print('конец программы:', b)

Вывод:

старт программы
имя: basic_function
док: Базовая функция
Базовая функция: 1
конец программы: 2

Функция — это вызываемый объект. Как можно догадаться, это подразумевает объект, который можно вызвать. Проверить эту особенность можно с помощью встроенной функции callable().

Декораторы

Итак, декоратор — это функция, которая принимает функцию, делает что-то и возвращает другую функцию.

					

def decorator(func):
def wrapper():
print('функция-оболочка')
func()
return wrapper

def basic():
print('основная функция')

wrapped = decorator(basic)
print('старт программы')
basic()
wrapped()
print('конец программы')

Подписывайтесь на телеграм каналы

Если запустить эту программу:

старт программы
основная функция
функция-оболочка
основная функция
конец программы

Разберемся с тем, что здесь произошло. Функция decorator — это, как можно понять по названию, декоратор. Она принимает в качестве параметра функцию func. Крайне оригинальные имена. Внутри функции объявляется другая под названием wrapper. Объявлять ее внутри необязательно, но так проще работать.

В этом примере функция wrapper просто вызывает оригинальную функцию, которая была передана в декоратор в качестве аргумента, но это может быть любая другая функциональность.

В конце возвращается функция wrapper. Напомним, что нам все еще нужен вызываемый объект. Теперь результат можно вызывать с оригинальным набором возможностей, а также новым включенным кодом.

Но в Python есть синтаксис для упрощения такого объявления. Чтобы декорировать функции, используется символ @ рядом с именем декоратора. Он размещается над функцией, которую требуется декорировать.

					

def decorator(func):
'''Основная функция'''
print('декоратор')
def wrapper():
print('-- до функции', func.__name__)
func()
print('-- после функции', func.__name__)
return wrapper

@decorator
def wrapped():
print('--- обернутая функция')

print('- старт программы...')
wrapped()
print('- конец программы')

Вывод:

декоратор
- старт программы...
-- до функции wrapped
--- обернутая функция
-- после функции wrapped
- конец программы

Можно использовать тот же декоратор с любым количеством функций, а также — декорировать функцию любым декоратором.

					

def decorator_1(func):
print('декоратор 1')
def wrapper():
print('перед функцией')
func()
return wrapper

def decorator_2(func):
print('декоратор 2')
def wrapper():
print('перед функцией')
func()
return wrapper

@decorator_1
@decorator_2
def basic_1():
print('basic_1')

@decorator_1
def basic_2():
print('basic_2')

print('>> старт')
basic_1()
basic_2()
print('>> конец')

Вывод:

декоратор 2
декоратор 1
декоратор 1
>> старт
перед функцией
перед функцией
basic_1
перед функцией
basic_2
>> конец

Когда у функции несколько декораторов, они вызываются в обратном порядке относительно того, как были вызваны. То есть, такой вызов:

@decorator_1
@decorator_2
def wrapped():

Равен следующему:

a = decorator_1(decorator_2(wrapped))

Если декорированная функция возвращает значение, и его нужно сохранить, то нужно сделать так, чтобы его возвращала и функция-обертка.

Декоратор-класс

Добавив метод __call__ в класс, его можно превратить в вызываемый объект. А поскольку декоратор — это всего лишь функция, то есть, вызываемый объект, класс можно превратить в декоратор с помощью функции __call__.

Лучше всего разобрать это на примере.

					

class Decorator:
def __init__(self, func):
print('> Класс Decorator метод __init__')
self.func = func

def __call__(self):
print('> перед вызовом класса...', self.func.__name__)
self.func()
print('> после вызова класса')

@Decorator
def wrapped():
print('функция wrapped')

print('>> старт')
wrapped()
print('>> конец')

Вывод:

> Класс Decorator метод __init__
>> старт
> перед вызовом класса... wrapped
функция wrapped
> после вызова класса
>> конец

Отличие в том, что класс инициализируется при объявлении. Он должен получить функцию в качестве аргумента для метода __init__. Это и будет декорируемая функция.

При вызове декорируемой функции на самом деле вызывается экземпляр класса. А поскольку объект вызываемый, то вызывается функция __call__.

Функция с аргументами

А что если функция, которую требуется декорировать, должна получать аргументы? Для этого нужно вернуть функцию с той же сигнатурой, что и у декорируемой.

					

def decorator_with_args(func):
print('> декоратор с аргументами...')
def decorated(a, b):
print('до вызова функции', func.__name__)
ret = func(a, b)
print('после вызова функции', func.__name__)
return ret
return decorated

@decorator_with_args
def add(a, b):
print('функция 1')
return a + b

@decorator_with_args
def sub(a, b):
print('функция 2')
return a - b

print('>> старт')
r = add(10, 5)
print('r:', r)
g = sub(10, 5)
print('g:', g)
print('>> конец')

Вывод:

> декоратор с аргументами...
> декоратор с аргументами...
>> старт
до вызова функции add
функция 1
после вызова функции add
r: 15
до вызова функции sub
функция 2
после вызова функции sub
g: 5
>> конец

А в случае с классом? Тот же принцип. Нужно лишь добавить желаемую сигнатуру в функцию __call__.

					

class Decorator:
def __init__(self, func):
print('> Класс Decorator метод __init__')
self.func = func

def __call__(self, a, b):
print('> до вызова из класса...', self.func.__name__)
self.func(a, b)
print('> после вызова из класса')

@Decorator
def wrapped(a, b):
print('функция wrapped:', a, b)

print('>> старт')
wrapped(10, 20)
print('>> конец')

Вывод:

> Класс Decorator метод __init__
>> старт
> до вызова из класса... wrapped
функция wrapped: 10 20
> после вызова из класса
>> конец

Можно использовать *args и **kwargs и для функции wrapper, если сигнатура заранее неизвестна, или будут приниматься разные типы функций.

Декораторы с аргументами

В декоратор можно передать и сам параметр. В этом случае нужно добавить еще один слой абстракции, то есть — еще одну функцию-обертку.

Это обязательно, поскольку аргумент передается декоратору. Затем функция, которая вернулась, используется для декорации нужной. Проще разобраться на примере.

					

def decorator_with_args(name):
print('> decorator_with_args:', name)
def real_decorator(func):
print('>> сам декоратор', func.__name__)
def decorated(*args, **kwargs):
print('>>> перед функцие', func.__name__)
ret = func(*args, **kwargs)
print('>>> после функции', func.__name__)
return ret
return decorated
return real_decorator

@decorator_with_args('test')
def add(a, b):
print('>>>> функция add')
return a + b

print('старт программы')
r = add(10, 10)
print(r)
print('конец программы')

Вывод:

> decorator_with_args: test
>> сам декоратор add
старт программы
>>> перед функцие add
>>>> функция add
>>> после функции add
20
конец программы

В декораторах-классах выполняются такие же настройки. Теперь конструктор класса получает все аргументы декоратора. Метод __call__ должен возвращать функцию-обертку, которая, по сути, будет выполнять декорируемую функцию. Например:

					

class DecoratorArgs:
def __init__(self, name):
print('> Декоратор с аргументами __init__:', name)
self.name = name

def __call__(self, func):
def wrapper(a, b):
print('>>> до обернутой функции')
func(a, b)
print('>>> после обернутой функции')
return wrapper

@DecoratorArgs("teste")
def add(a, b):
print('функция add:', a, b)

print('>> старт')
add(10, 20)
print('>> конец')

Вывод:

> Декоратор с аргументами __init__: teste
>> старт
>>> до обернутой функции
функция add: 10 20
>>> после обернутой функции
>> конец

Документация

Один из атрибутов функции — строка документации (docstring), доступ к которой можно получить с помощью __doc__. Это строковая константа, определяемая как первая инструкция в объявлении функции.

При декорации возвращается новая функция с другими атрибутами. Но они не изменяются.

					

def decorator(func):
'''Декоратор'''
def decorated():
'''Функция Decorated'''
func()
return decorated

@decorator
def wrapped():
'''Оборачиваемая функция'''
print('функция wrapped')

print('старт программы...')
print(wrapped.__name__)
print(wrapped.__doc__)
print('конец программы')

В этом примере функция wrapped — это, по сути, функция decorated, которую она заменяет.

старт программы...
decorated
Функция Decorated
конец программы

Вот где на помощь приходит функция wraps из модуля functools. Она сохраняет атрибуты оригинальной функции. Нужно лишь декорировать функцию wrapper с ее помощью.

					

from functools import wraps

def decorator(func):
'''Декоратор'''
@wraps(func)
def decorated():
'''Функция Decorated'''
func()
return decorated

@decorator
def wrapped():
'''Оборачиваемая функция'''
print('функция wrapped')

print('старт программы...')
print(wrapped.__name__)
print(wrapped.__doc__)
print('конец программы')

Вывод:

старт программы...
wrapped
Оборачиваемая функция
конец программы

Приложения

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

Таймеры

Базовая функциональность — время работы функции. Есть возможность получить время до и после вызова функции, использовав полученный результат (для записи в лог, базу данных, для отладки и так далее).

					

from datetime import datetime
import time

def elapsed(func):
def wrapper(a, b, delay=0):
start = datetime.now()
func(a, b, delay)
end = datetime.now()
elapsed = (end - start).total_seconds() * 1000
print(f'>> функция {func.__name__} время выполнения (ms): {elapsed}')
return wrapper

@elapsed
def add_with_delay(a, b, delay=0):
print('сложить', a, b, delay)
time.sleep(delay)
return a + b

print('старт программы')
add_with_delay(10, 20)
add_with_delay(10, 20, 1)
print('конец программы')

Вывод:

старт программы
сложить 10 20 0
>> функция add_with_delay время выполнения (ms): 36.006
сложить 10 20 1
>> функция add_with_delay время выполнения (ms): 1031.255
конец программы

Логи

Еще один распространенный сценарий применения для декоратора — логирование функций.

					

import logging

def logger(func):
log = logging.getLogger(__name__)
def wrapper(a, b):
log.info("Вызов функции ", func.__name__)
ret = func(a, b)
log.info("Вызвана функция ", func.__name__)
return ret
return wrapper

@logger
def add(a, b):
print('a + b:', a + b)
return a + b

print('>> старт')
add(10, 20)
add(20, 30)
print('>> конец')

Функция обратного вызова

Функция обратного вызова — это функция, которая вызывается при срабатывании определенного события (переходе на страницу, получении сообщения или окончании обработки процессором).

Можно передать функцию, чтобы она выполнилась после определенного события. Это используется, например, в HTTP-серверах в ответ на URL-запросы.

					

app = {}

def callback(route):
def wrapper(func):
app[route] = func
def wrapped():
ret = func()
return ret
return wrapped
return wrapper

@callback('/')
def index():
print('index')
return 'OK'

print('> старт')
route = app.get('/')
if route:
resp = route()
print('ответ:', resp)

print('> конец')

Проверка состояний

Декораторы можно использовать для проверки состояния перед выполнение функции: например, зарегистрирован ли пользователь, есть ли у него достаточное количество прав или валидны ли аргументы (типы, значения и так далее).

					

user_permissions = ["user"]

def check_permission(permission):
def wrapper_permission(func):
def wrapped_check():
if permission not in user_permissions:
raise ValueError("Недостаточно прав")
return func()
return wrapped_check
return wrapper_permission

@check_permission("user")
def check_value():
return "значение"

@check_permission("admin")
def do_something():
return "только админ"

print('старт программы')
check_value()
do_something()
print('конец программы')

Вывод:

Traceback (most recent call last):
  File "C:\Programs\Python\Python38-32\test.py", line 22, in <module>
    do_something()
  File "C:\Programs\Python\Python38-32\test.py", line 7, in wrapped_check
    raise ValueError("Недостаточно прав")
ValueError: Недостаточно прав

Создание singleton

Декоратор можно использовать для декорирования класса. Отличие лишь в том, что декоратор получает класс, а не функцию.

Singleton — это класс с одним экземпляром. Его можно сохранить как атрибут функции-обертки и вернуть при запросе. Это полезно в тех случаях, когда, например, ведется работа с соединением с базой данных.

					

def singleton(cls):
'''Класс Singleton (один экземпляр)'''
def wrapper_singleton(*args, **kwargs):
if not wrapper_singleton.instance:
wrapper_singleton.instance = cls(*args, **kwargs)
return wrapper_singleton.instance
wrapper_singleton.instance = None
return wrapper_singleton

@singleton
class TheOne:
pass

print('старт')
first_one = TheOne()
second_one = TheOne()
print(id(first_one))
print(id(second_one))
print('конец')

Вывод:

старт
56909912
56909912
конец

Обработка ошибок

Можно убедиться, что обрабатываются определенные типы ошибок без использования блока try для каждой функции. Результат после этого добавляется в лог или останавливает выполнение программы.

					

def error_handler(func):
def wrapper(*args, **kwargs):
ret = 0
try:
ret = func(*args, **kwargs)
except:
print('>> Ошибка в функции', func.__name__)
return ret
return wrapper

@error_handler
def div(a, b):
return a / b

print('старт')
print(div(10, 2))
print(div(10, 0))
print('конец')

Вывод:

старт
5.0
>> Ошибка в функции div
0
конец

Выводы

В этом материале была разобрана тема декораторов в Python. Это удобный инструмент, который улучшает код тем, что делает его проще и гибче.

Вот некоторые из особенностей декораторов:

  • Их можно использовать повторно.
  • Они могут получать параметры и возвращать значения.
  • Могут хранить значения/
  • Могут декорировать классы.
  • Они могут добавлять функциональность в другие функции или классы.

В Python есть встроенные декораторы: classmethod, property и staticmethod.

Декораторы можно использовать и с другими методами (например, «магическими»), чтобы расширить возможности классов и всего проекта.

Понимаем декораторы в Python’e, шаг за шагом. Шаг 2 / Хабр

И снова доброго времени суток всем читателям!

Спасибо, за проявленый интерес к первой части перевода, надеюсь, вторая вас так же не разочарует.

Итак, в первой части данной статьи мы совершили базовое знакомство с декораторами, принципами их работы и даже написали свой вручную.

Однако, все декораторы, которые мы до этого рассматривали не имели одного очень важного функционала — передачи аргументов декорируемой функции.

Что ж, исправим это недоразумение!

Передача («проброс») аргументов в декорируемую функцию

Никакой чёрной магии, всё, что нам необходимо — собственно, передать аргументы дальше!

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2): # аргументы прибывают отсюда
        print "Смотри, что я получил:", arg1, arg2
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments
 
# Теперь, когда мы вызываем функцию, которую возвращает декоратор,
# мы вызываем её "обёртку", передаём ей аргументы и уже в свою очередь
# она передаёт их декорируемой функции
 
@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print "Меня зовут", first_name, last_name
 
print_full_name("Питер", "Венкман")
# выведет:
# Смотри, что я получил: Питер Венкман
# Меня зовут Питер Венкман
# *

* — Прим. переводчика: Питер Венкман — имя одного из Охотников за приведениями, главного героя одноименного культового фильма.

Декорирование методов

Один из важных фактов, которые следует понимать, заключается в том, что функции и методы в Python’e — это практически одно и то же, за исключением того, что методы всегда ожидают первым параметром ссылку на сам объект (self). Это значит, что мы можем создавать декораторы для методов так же, как и для функций, просто не забывая про self.

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # действительно, дружелюбно - снизим возраст ещё сильней :-)
        return method_to_decorate(self, lie)
    return wrapper
 
 
class Lucy(object):
 
    def __init__(self):
        self.age = 32
 
    @method_friendly_decorator
    def sayYourAge(self, lie):
        print "Мне %s, а ты бы сколько дал?" % (self.age + lie)
 
l = Lucy()
l.sayYourAge(-3)
# выведет: Мне 26, а ты бы сколько дал?

Конечно, если мы создаём максимально общий декоратор и хотим, чтобы его можно было применить к любой функции или методу, то стоит воспользоваться тем, что *args распаковывает список args, а **kwargs распаковывает словарь kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # Данная "обёртка" принимает любые аргументы
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print "Передали ли мне что-нибудь?:"
        print args
        print kwargs
        # Теперь мы распакуем *args и **kwargs
        # Если вы не слишком хорошо знакомы с распаковкой, можете прочесть следующую статью:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments
 
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print "Python is cool, no argument here." # оставлено без перевода, хорошая игра слов:)
 
function_with_no_argument()
# выведет:
# Передали ли мне что-нибудь?:
# ()
# {}
# Python is cool, no argument here.
 
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print a, b, c
 
function_with_arguments(1,2,3)
# выведет:
# Передали ли мне что-нибудь?:
# (1, 2, 3)
# {}
# 1 2 3
 
@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Почему нет?"):
    print "Любят ли %s, %s и %s утконосов? %s" %\
    (a, b, c, platypus)
 
function_with_named_arguments("Билл", "Линус", "Стив", platypus="Определенно!")
# выведет:
# Передали ли мне что-нибудь?:
# ('Билл', 'Линус', 'Стив')
# {'platypus': 'Определенно!'}
# Любят ли Билл, Линус и Стив утконосов? Определенно!
 
class Mary(object):
 
    def __init__(self):
        self.age = 31
 
    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # Теперь мы можем указать значение по умолчанию
        print "Мне %s, а ты бы сколько дал?" % (self.age + lie)
 
m = Mary()
m.sayYourAge()
# выведет:
# Передали ли мне что-нибудь?:
# (<__main__ .Mary object at 0xb7d303ac>,)
# {}
# Мне 28, а ты бы сколько дал?
Вызов декоратора с различными аргументами

Отлично, с этим разобрались. Что вы теперь скажете о том, чтобы попробовать вызывать декораторы с различными аргументами?

Это не так просто, как кажется, поскольку декоратор должен принимать функцию в качестве аргумента, и мы не можем просто так передать ему что либо ещё.

Так что, перед тем, как показать вам решение, я бы хотел освежить в памяти то, что мы уже знаем:

# Декораторы - это просто функции
def my_decorator(func):
    print "Я обычная функция"
    def wrapper():
        print "Я - функция, возвращаемая декоратором"
        func()
    return wrapper
 
# Так что, мы можем вызывать её, не используя "@"-синтаксис:
 
def lazy_function():
    print "zzzzzzzz"
 
decorated_function = my_decorator(lazy_function)
# выведет: Я обычная функция
 
# Данный код выводит "Я обычная функция", потому что это ровно то, что мы сделали:
# вызвали функцию. Ничего сверхъестественного
 
@my_decorator
def lazy_function():
    print "zzzzzzzz"
 
# выведет: Я обычная функция

Как мы видим, это два аналогичных действия. Когда мы пишем

@my_decorator

— мы просто говорим интерпретатору «вызвать функцию, под названием my_decorator». Это важный момент, потому что данное название может как привести нас напрямую к декоратору… так и нет!
Давайте сделаем нечто страшное!:)

def decorator_maker():
 
    print "Я создаю декораторы! Я буду вызван только раз: "+\
          "когда ты попросишь меня создать тебе декоратор."
 
    def my_decorator(func):
 
        print "Я - декоратор! Я буду вызван только раз: в момент декорирования функции."
 
        def wrapped():
            print ("Я - обёртка вокруг декорируемой функции. "
                  "Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. "
                  "Я возвращаю результат работы декорируемой функции.")
            return func()
 
        print "Я возвращаю обёрнутую функцию."
 
        return wrapped
 
    print "Я возвращаю декоратор."
    return my_decorator
 
# Давайте теперь создадим декоратор. Это всего лишь ещё один вызов функции
new_decorator = decorator_maker()
# выведет:
# Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. 
# Я возвращаю декоратор.
 
# Теперь декорируем функцию
 
def decorated_function():
    print "Я - декорируемая функция."
 
decorated_function = new_decorator(decorated_function)
# выведет:
# Я - декоратор! Я буду вызван только раз: в момент декорирования функции.
# Я возвращаю обёрнутую функцию.
 
# Теперь наконец вызовем функцию:
decorated_function()
# выведет:
# Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию.
# Я возвращаю результат работы декорируемой функции.
# Я - декорируемая функция.

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

def decorated_function():
    print "Я - декорируемая функция."
decorated_function = decorator_maker()(decorated_function)
# выведет:
# Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. 
# Я возвращаю декоратор.
# Я - декоратор! Я буду вызван только раз: в момент декорирования функции.
# Я возвращаю обёрнутую функцию.
 
# Наконец:
decorated_function()
# выведет:
# Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию.
# Я возвращаю результат работы декорируемой функции.
# Я - декорируемая функция.

А теперь ещё раз, ещё короче:

@decorator_maker()
def decorated_function():
    print "I am the decorated function."
# выведет:
# Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. 
# Я возвращаю декоратор.
# Я - декоратор! Я буду вызван только раз: в момент декорирования функции.
# Я возвращаю обёрнутую функцию.
 
# И снова:
decorated_function()
# выведет:
# Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию.
# Я возвращаю результат работы декорируемой функции.
# Я - декорируемая функция.

Вы заметили, что мы вызвали функцию, после знака «@»?:)

Вернёмся, наконец, к аргументам декораторов, ведь если мы используем функцию, чтобы создавать декораторы «на лету», мы можем передавать ей любые аргументы, верно?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
 
    print "Я создаю декораторы! И я получил следующие аргументы:", decorator_arg1, decorator_arg2
 
    def my_decorator(func):
        print "Я - декоратор. И ты всё же смог передать мне эти аргументы:", decorator_arg1, decorator_arg2
 
        # Не перепутайте аргументы декораторов с аргументами функций!
        def wrapped(function_arg1, function_arg2) :
            print ("Я - обёртка вокруг декорируемой функции.\n"
                  "И я имею доступ ко всем аргументам: \n"
                  "\t- и декоратора: {0} {1}\n"
                  "\t- и функции: {2} {3}\n"
                  "Теперь я могу передать нужные аргументы дальше"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)
 
        return wrapped
 
    return my_decorator
 
@decorator_maker_with_arguments("Леонард", "Шелдон")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("Я - декорируемая функция и я знаю только о своих аргументах: {0}"
           " {1}".format(function_arg1, function_arg2))
 
decorated_function_with_arguments("Раджеш", "Говард")
# выведет:
# Я создаю декораторы! И я получил следующие аргументы: Леонард Шелдон
# Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Шелдон
# Я - обёртка вокруг декорируемой функции.
# И я имею доступ ко всем аргументам: 
#   - и декоратора: Леонард Шелдон
#   - и функции: Раджеш Говард
# Теперь я могу передать нужные аргументы дальше
# Я - декорируемая функция и я знаю только о своих аргументах: Раджеш Говард

* — Прим. переводчика: в данном примере автор упоминает имена главных героев популярного сериала «Теория Большого взрыва».
Вот он, искомый декоратор, которому можно передавать произвольные аргументы.
Безусловно, аргументами могут быть любые переменные:

c1 = "Пенни"
c2 = "Лесли"
 
@decorator_maker_with_arguments("Леонард", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("Я - декорируемая функция и я знаю только о своих аргументах: {0}"
           " {1}".format(function_arg1, function_arg2))
 
decorated_function_with_arguments(c2, "Говард")
# выведет:
# Я создаю декораторы! И я получил следующие аргументы: Леонард Пенни
# Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Пенни
# Я - обёртка вокруг декорируемой функции.
# И я имею доступ ко всем аргументам: 
#   - и декоратора: Леонард Пенни
#   - и функции: Лесли Говард
# Теперь я могу передать нужные аргументы дальше
# Я - декорируемая функция и я знаю только о своих аргументах: Лесли Говард

Таким образом, мы можем передавать декоратору любые аргументы, как обычной функции. Мы можем использовать и распаковку через *args и **kwargs в случае необходимости.
Но необходимо всегда держать в голове, что декоратор вызывается ровно один раз. Ровно в момент, когда Python импортирует Ваш скрипт. После этого мы уже не можем никак изменить аргументы, с которыми.
Когда мы пишем «import x» все функции из x декорируются сразу же, и мы уже не сможем ничего изменить.

Немного практики: напишем декоратор декорирующий декоратор

Если вы дочитали до этого момента и ещё в строю — вот вам бонус от меня.
Это небольшая хитрость позволит вам превратить любой обычный декоратор в декоратор, принимающий аргументы.
Изначально, чтобы получить декоратор, принимающий аргументы, мы создали его с помощью другой функции.
Мы обернули наш декоратор.
Есть ли у нас что-нибудь, чем можно обернуть функцию?
Точно, декораторы!

Давайте же немного развлечёмся и напишем декоратор для декораторов:

def decorator_with_args(decorator_to_enhance):
    """
    Эта функция задумывается КАК декоратор и ДЛЯ декораторов.
    Она должна декорировать другую функцию, которая должна быть декоратором.
    Лучше выпейте чашку кофе.
    Она даёт возможность любому декоратору принимать произвольные аргументы,
    избавляя Вас от головной боли о том, как же это делается, каждый раз, когда этот функционал необходим.
    """
 
    # Мы используем тот же трюк, который мы использовали для передачи аргументов:
    def decorator_maker(*args, **kwargs):
 
        # создадим на лету декоратор, который принимает как аргумент только 
        # функцию, но сохраняет все аргументы, переданные своему "создателю"
        def decorator_wrapper(func):
 
            # Мы возвращаем то, что вернёт нам изначальный декоратор, который, в свою очередь
            # ПРОСТО ФУНКЦИЯ (возвращающая функцию).
            # Единственная ловушка в том, что этот декоратор должен быть именно такого
            # decorator(func, *args, **kwargs)
            # вида, иначе ничего не сработает
            return decorator_to_enhance(func, *args, **kwargs)
 
        return decorator_wrapper
 
    return decorator_maker

Это может быть использовано так:

# Мы создаём функцию, которую будем использовать как декоратор и декорируем её :-)
# Не стоит забывать, что она должна иметь вид "decorator(func, *args, **kwargs)"
@decorator_with_args
def decorated_decorator(func, *args, **kwargs):
    def wrapper(function_arg1, function_arg2):
        print "Мне тут передали...:", args, kwargs
        return func(function_arg1, function_arg2)
    return wrapper
 
# Теперь декорируем любую нужную функцию нашим новеньким, ещё блестящим декоратором:
 
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print "Привет", function_arg1, function_arg2
 
decorated_function("Вселенная и", "всё прочее")
# выведет:
# Мне тут передали...: (42, 404, 1024) {}
# Привет Вселенная и всё прочее
 
# Уфффффф!

Думаю, я знаю, что Вы сейчас чувствуете.
Последний раз Вы испытывали это ощущение, слушая, как вам говорят: «Чтобы понять рекурсию необходимо для начала понять рекурсию».
Но ведь теперь Вы рады, что разобрались с этим?;)

Рекомендации для работы с декораторами

  • Декораторы были введены в Python 2.4, так что узнавайте, на чём будет выполняться Ваш код.
  • Декораторы несколько замедляют вызов функции, не забывайте об этом.
  • Вы не можете «раздекорировать» функцию. Безусловно, существуют трюки, позволяющие создать декоратор, который можно отсоединить от функции, но это плохая практика. Правильней будет запомнить, что если функция декорирована — это не отменить.
  • Декораторы оборачивают функции, что может затруднить отладку.

Последняя проблема частично решена в Python 2.5, добавлением в стандартную библиотеку модуля functools включающего в себя functools.wraps, который копирует всю информацию об оборачиваемой функции (её имя, из какого она райомодуля, её docstrings и т.п.) в функцию-обёртку.
Забавным фактом является то, что functools.wraps — сам по себе декоратор.

# Во время отладки, в трассировочную информацию выводится __name__ функции.
def foo():
    print "foo"
 
print foo.__name__
# выведет: foo
 
# Однако, декораторы мешают нормальному ходу дел:
def bar(func):
    def wrapper():
        print "bar"
        return func()
    return wrapper
 
@bar
def foo():
    print "foo"
 
print foo.__name__
# выведет: wrapper
 
# "functools" может нам с этим помочь
 
import functools
 
def bar(func):
    # Объявляем "wrapper" оборачивающим "func"
    # и запускаем магию:
    @functools.wraps(func)
    def wrapper():
        print "bar"
        return func()
    return wrapper
 
@bar
def foo():
    print "foo"
 
print foo.__name__
# выведет: foo

Как можно использовать декораторы?

И в заключение, я бы хотел ответить на вопрос, который я часто слышу: зачем же нужны декораторы? Как их можно использовать?
Декораторы могут быть использованы для расширения возможностей функций из сторонних библиотек (код которых мы не можем изменять), или для упрощения отладки (мы не хотим изменять код, который ещё не устоялся).
Так же полезно использовать декораторы для расширения различных функций одним и тем же кодом, без повторного его переписывания каждый раз, например:

def benchmark(func):
    """
    Декоратор, выводящий время, которое заняло
    выполнение декорируемой функции.
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print func.__name__, time.clock() - t
        return res
    return wrapper
 
 
def logging(func):
    """
    Декоратор, логирующий работу кода.
    (хорошо, он просто выводит вызовы, но тут могло быть и логирование!)
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print func.__name__, args, kwargs
        return res
    return wrapper
 
 
def counter(func):
    """
    Декоратор, считающий и выводящий количество вызовов
    декорируемой функции.
    """
    def wrapper(*args, **kwargs):
        wrapper.count += 1
        res = func(*args, **kwargs)
        print "{0} была вызвана: {1}x".format(func.__name__, wrapper.count)
        return res
    wrapper.count = 0
    return wrapper
 
 
@benchmark
@logging
@counter
def reverse_string(string):
    return str(reversed(string))
 
print reverse_string("А роза упала на лапу Азора")
print reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!")
 
# выведет:
# reverse_string ('А роза упала на лапу Азора',) {}
# wrapper 0.0
# reverse_string была вызвана: 1x
# арозА упал ан алапу азор А
# reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
# wrapper 0.0
# reverse_string была вызвана: 2x
# !amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

Таким образом, декораторы можно применить к любой функции, расширив её функционал и не переписывая ни строчки кода!

import httplib
 
@benchmark
@logging
@counter
def get_random_futurama_quote():
    conn = httplib.HTTPConnection("slashdot.org:80")
    conn.request("HEAD", "/index.html")
    for key, value in conn.getresponse().getheaders():
        if key.startswith("x-b") or key.startswith("x-f"):
            return value
    return "Эх, нет... не могу!"
 
print get_random_futurama_quote()
print get_random_futurama_quote()
 
#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#get_random_futurama_quote была вызвана: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#get_random_futurama_quote была вызвана: 2x
#Curse you, merciful Poseidon!

В Python включены такие декораторы как property, staticmethod и т.д.
В Django декораторы используются для управления кешированием, контроля за правами доступа и определения обработчиков адресов. В Twisted — для создания поддельных асинхронных inline-вызовов.
Декораторы открывают широчайший простор для экспериментов! И надеюсь, что данная статья поможет Вам в его освоении!
Спасибо за внимание!

Содержание:

Прим. переводчика:

Буду благодарен любым замечаниям по переводу и оформлению. Надеюсь, вторая часть статьи покажется Вам более неочевидной и полезной, чем первая.

О декораторах в Python / Блог компании OTUS. Онлайн-образование / Хабр

Всем привет!

Перевод статьи подготовлен для студентов курса «Web-разработчик на Python». Интересно развиваться в данном направлении? Запишитесь на День Открытых Дверей курса и пообщайтесь вживую с преподавателем: онлайн-трансляция 23 июля в 20:00 по мск.!

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

Давайте рассмотрим пример простого декоратора логина, который проверяет, что пользователь вошел в систему, прежде чем давать ему возможности редактировать посты. Потом декоратор перенаправляет на страницу входа в систему или регистрации, а затем с помощью правильно заданных параметров возвращает обратно на ту же страницу после успешного прохождения идентификации. Чтобы воспользоваться данной функцией, вам нужно всего лишь написать @login_required перед целевой функцией.

@login_required
def edit_post(post_id):
...

С декораторами очень легко работать, но создание декораторов задача непростая даже для опытных Python-программистов. В этой статье мы с вами рассмотрим пошагово как работают декораторы в Python.

Функции

Функции также называются объектами первого класса в Python. Функции это такие же значения, как числа, списки и строки, как видно из следующего примера.

>>> def foo():
...     return 1
...
>>>
>>> foo()
1
>>>

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

>>>
>>> a_string = "This is a global variable"
>>>
>>> def foo():
...     print(locals())
...
>>>
>>> print(globals())
{..., 'a_string': 'This is a global variable'}
>>>
>>> foo() # 2
{}
>>>

Область видимости функции как переменная

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

>>>
>>> a_string = "This is a global variable"
>>>
>>> def foo():
...     print(a_string) #1
...
>>>
>>> foo()
This is a global variable
>>>

Время жизни переменной

В пространстве имен живут не только переменные, но и они имеют время жизни, о котором важно помнить. Рассмотрим пример, который иллюстрирует не только правила области видимости и те проблемы, которые они могут вызвать, но и то, как они взаимодействуют с вызовом функции и то как они работают в Python и других языках.

>>> def foo():
...     x = 1
...
>>> foo()
>>>
>>> print(x) # 1
Traceback (most recent call last):
  ...
NameError: name 'x' is not defined
>>>

Вложенные функции

В Python можно создавать вложенные функции, а это значит, что мы можем объявлять функции внутри функций, и все правила области видимости и времени жизни по-прежнему действуют.

>>>
>>> def outer():
...     x = 1
...     def inner():
...         print(x) # 1
...     inner() # 2
...
>>> outer()
1
>>>

Декораторы

Замыкание (closure), которое принимает функцию в качестве параметра и возвращает функцию, называется декоратором. Рассмотрим пример полезных декораторов.

>>>
>>> def outer(some_func):
...     def inner():
...         print("before some_func")
...         ret = some_func() # 1
...         return ret + 1
...     return inner
...
>>> def foo():
...     return 1
...
>>> decorated = outer(foo) # 2
>>>
>>> decorated()
before some_func
2
>>>

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

>>>
>>> foo = outer(foo)
>>>
>>> foo # doctest: +ELLIPSIS
<function outer.<locals>.inner at 0x...>
>>>

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

Учебник по декораторам Python — Настоящий Python