Декораторы 1.2

Декораторы в Python и примеры их практического использования.

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

Вспомнив это, можно смело переходить к декораторам. Декораторы — это, по сути, "обёртки", которые дают нам возможность изменить поведение функции, не изменяя её код.

Создадим свой декоратор "вручную":

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

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

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

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

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

То есть, декораторы в python — это просто синтаксический сахар для конструкций вида:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

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

>>>
>>> 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()
#помидоры#

--ветчина--
<\______/>
~салат~

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

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

>>>
>>> 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("Vasya", "Pupkin")
Смотри, что я получил: Vasya Pupkin
Меня зовут Vasya Pupkin

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

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

>>>
>>> def method_friendly_decorator(method_to_decorate):
...  def wrapper(self, lie):
...  lie -= 3
...  return method_to_decorate(self, lie)
...  return wrapper
...
>>> class Lucy:
...  def __init__(self):
...  self.age = 32
...  @method_friendly_decorator
...  def sayYourAge(self, lie):
...  print("Мне {} лет, а ты бы сколько дал?".format(self.age + lie))
...
>>> l = Lucy()
>>> l.sayYourAge(-3)
Мне 26 лет, а ты бы сколько дал?

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

>>>
>>> def a_decorator_passing_arbitrary_arguments(function_to_decorate):
...  # Данная "обёртка" принимает любые аргументы
...  def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
...  print("Передали ли мне что-нибудь?:")
...  print(args)
...  print(kwargs)
...  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("Любят ли {}, {} и {} утконосов? {}".format(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("Мне {} лет, а ты бы сколько дал?".format(self.age + lie))
...
>>> m = Mary()
>>> m.sayYourAge()
Передали ли мне что-нибудь?:
(<__main__.Mary object at 0x7f6373017780>,)
{}
Мне 28 лет, а ты бы сколько дал?

 

Категория: Python | Добавил: ghost_mod (08.04.2016)
Просмотров: 373 | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email:
Подписка:1
Код *: