Python-декоратори - це що таке?
Декоратор і функція
Декоратори в Python - це функція, яка використовує іншу функцію як аргумент. Являє собою блок коду, який повертає деяке значення. Містить аргументи, вони будуть використовуватися в подальшому вплинуть на значення, що повертається. Результат, який виходить на виході, може бути будь-якого типу: список, кортеж, функція. У "Пайтоне" кожна функція - об'єкт, оголошується він за допомогою ключового слова def. Область значень задається не фігурними дужками, а позначається відступом tab. Після ключового слова вказується ім'я, аргументи задаються в дужках () після імені. Перед переходом на новий рядок ставиться символ ":". У "Пайтоне" тіло не може бути порожнім, повинно обов'язково містити список команд. Якщо потрібно залишити це місце ставиться порожній оператор pass. def empty_func():
pass
Подібний синтаксис поширюється на всі функції, крім анонімною. Анонімна виглядає так:
func = lambda x, y: x + y
Виклик:
func(1 2) #повертає 3
Виклик (другий спосіб):
(lambda x, y: x + y)(1 2) #повертає 3
Декоратори викликаються так:
@ім'я декоратора
def изменяемая_функция
тіло изменяемой_функции
Схема роботи описується наступним кодом:
def decorator(change_funct):
def return_func1():
print "code before"
change_funct()
print "code after"
return return_func1
Відповідно, виклик відбувається наступним чином:
<!-- fb_336x280_2 -->
<script> (adsbygoogle = window.adsbygoogle ||[]).push({});
@decorator
def retrurn_func1():
print new_change
Аргументи функції
В аргументах декораторів Python передається будь-який тип даних.
Змінні одного типу перераховуються через кому. Є кілька способів того, як призначаються значення змінним, зазначені у параметрах.
- Звичайний.
- За допомогою ключових слів.
- Завдання статичних значень.
- Використання позиційних елементів.
При створенні вказується кілька аргументів в певному порядку. При виклику у параметрах вказуються всі значення у відповідному порядку.
def bigger(a,b):
if a > b:
print a
else:
print b
Правильний виклик:
bigger(56)
Неправильний виклик:
bigger(3)
bigger(1273)
Якщо використовуються аргументи є ключовими словами, їх виклик здійснюється в довільному порядку, так як використовується значення визначає конкретне ім'я-ключ.
def person(name, age):
print name, "is", "age", "years old"
person(age=23 name="John")
Статичні значення змінних створюються разом з функцією через оператор присвоювання так, як якщо б ініціалізація відбувалася в тілі.
<script type="text/jаvascript">
var blockSettings2 = {blockId:"R-A-70350-39",renderTo:"yandex_rtb_R-A-70350-39",async:!0};
if(document.cookie.indexOf("abmatch=") >= 0) blockSettings2.statId = 70350;
!function(a,b,c,d,e){a[c]=a[c]||[],a[c].push(function(){Ya.Context.AdvManager.render(blockSettings2)}),e=b.getElementsByTagName("script")[0],d=b.createElement("script"),d.type="text/jаvascript",d.src="//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");
def space(planet_name, center="Star"):
print(planet_name, "is orbiting a", center)
space("Mars")
Коли кількість аргументів на етапі створення функції невідомо, використовуються аргументи. Вони можуть позначати кілька змінних одного типу або список:
def func(*args):
return args
func(123 'abc')
# (123 'abc')
func(1)
#(1)
Схожим способом передаються бібліотеки значень з ключами - з допомогою символу "**".
Змінні, зазначені в тілі, є локальними, використовуються безпосередньо самою функцією. Для створення глобальної змінної застосовується спецификатор global.
def get_older():
global age
age += 1
Підтримується рекурсія.
def fact(num):
if num == 0:
return 1
else:
return num * fact(num - 1)
Декорування методів
Функції і методи синтаксично схожі.
Різниця в тому, що функція викликається тільки по імені.
func()
А виклик методу здійснюється через оператора "." і вводиться ім'я методу, де першим параметром є батько.
object.func()
Таким чином, декоратори Python для методів створюються так само, як для функції.
Тут ми створили декоратор def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 #
return method_to_decorate(self, lie)
return wrapper
F тут створили клас з методами, які пізніше будуть модифіковані^
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def sayYourAge(self, lie):
print "Мені %s, а ти скільки дав?" % (self.age + lie)
Lucy.sayYourAge(-32)
#Мені 26 а ти скільки дав?
Тут використаний format(). Призначається для форматування рядків, використовується в такому вигляді:
<script type="text/jаvascript">
var blockSettings3 = {blockId:"R-A-70350-44",renderTo:"yandex_rtb_R-A-70350-44",async:!0};
if(document.cookie.indexOf("abmatch=") >= 0) blockSettings3.statId = 70350;
!function(a,b,c,d,e){a[c]=a[c]||[],a[c].push(function(){Ya.Context.AdvManager.render(blockSettings3)}),e=b.getElementsByTagName("script")[0],d=b.createElement("script"),d.type="text/jаvascript",d.src="//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");
print("string {} {}").format(1 2)
#string 1 2
В цьому прикладі в аргументах format() вказані дві цифри: 1 2. Вони заміщають символи {} в такому порядку, в якому розташовані. Форматування доступно виключно для рядкових елементів. Тобто, аргумент стає на перше місце фігурних дужок {}, а другий - друге, відповідно. У методі format передбачена можливість змінювати порядок вставки значень. Це робиться через індекси.
Якщо:
print("string {} {}").format(1 2)
#string 1 2
: R3r3r3701.
print("string {1} {0}").format(1 2)
#string 2 1
Допускається форматування рядків через ключові імена в форматі format(arg1 = value1 arg2 = value2).
print("string {arg1} {arg2}").format(arg1 = 1 arg2 = 2)
#string 1 2
Можна використовувати змішану систему - коли при двох аргументах тільки один з них має статичне значення. Для передачі значень указується індекс і ім'я змінної.
<script> (adsbygoogle = window.adsbygoogle ||[]).push({});
print("string {arg1} {1}").format(arg1 = 1 2)
#string 1 2
Декоратори з аргументами
Декораторам Python можна передати аргументи, які згодом модифікують оброблювану функцію.
def decorator(function_to_decorate):
def function(arg1 arg2):
print "Дивись, що я отримав:", arg1 arg2
return function
В даному випадку є @decorator, який модифікує function. Аргументи пробрасываются у другому рядку, передаються змінною function_to_decorate.
Виклик:
@decorator
def real_func(Петро Іванович)
print "Мене звати", arg1 arg2
На екрані з'явиться:
Дивись, що я отримав: Петро Іванович
Мене звати Петро Іванович
Вкладені декоратори
Коли не достатньо одного декоратора, реалізовується кілька рівнів обгортання. При створенні вкладеного декоратора кожен починається з нового рядка, кількість рядків визначає рівень складності. Виглядає так:
@AAA
@BBB
@CCC
def function():
pass:
Відповідно, AAA(), приймає в параметрах BBB(), а вона, обробляє CCC().
def f():
pass:
f = (AAA BBB(CCC(function))):
Function передається трьома різними декоратором, присвоюється f(). Кожен з них повертає свій результат, який, у свою чергу, обробляє обгортку. Можна помітити, що останній декоратор списку є першим, він починає обробляти function().
В Python декоратори класу виглядають також.
@firsdecorator
@seconddecorator
class CDC:
pass:
C = firstdecorator(seconddecorator(CDC))
XX = C()
def fn1(arg): return lambda: 'XX' + arg()
def fn2(arg): return lambda: 'YY' + arg()
def fn3(arg): return lambda: 'ZZ' + arg()
@fn1
@fn2
@fn3
def myfunc(): # myfunc = fn1(fn2(fn3(myfunc)))
return 'Python'
print(myfunc()) # Виведе "XXYYZZPython"
В даному випадку реалізація обгортають логіки відбувається шляхом використання def lambda:
<script type="text/jаvascript">
var blockSettings = {blockId:"R-A-70350-45",renderTo:"yandex_rtb_R-A-70350-45",async:!0};
if(document.cookie.indexOf("abmatch=") >= 0) blockSettings.statId = 70350;
!function(a,b,c,d,e){a[c]=a[c]||[],a[c].push(function(){Ya.Context.AdvManager.render(blockSettings)}),e=b.getElementsByTagName("script")[0],d=b.createElement("script"),d.type="text/jаvascript",d.src="//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");
lambda: 'XX' + arg()
Fn3() обертає myfunc, повертає ZZPython замість попереднього рядка Python. Потім починає працювати реверсивна fn2(), яка в підсумку повертає результат YYZZPython. В кінці fn1() обробляє myfunc() і повертає кінцевий результат - рядок XXYYZZPython.
Вбудовані декоратори
Існують вбудовані декоратори функцій Python. Вони поставляються в комплекті з інтерпретатором, для їх використання потрібно імпортувати додаткові модулі.
Staticmethod обробляє функцію-аргумент так, що вона стає статичної та приймає спецификатор static.
class C:
@staticmethod
def f(arg1 arg2 ): #static
pass
Classmethod робить з оброблюваної функції клас.
class MyClass:
@classmethod
def method(cls, arg):
print('%s classmethod. %d' % (cls.__name__, arg))
@classmethod
def call_original_method(cls):
cls.method(5)
def call_class_method(self):
self.method(10)
class MySubclass(MyClass):
@classmethod
def call_original_method(cls):
cls.method(6)
MyClass.method(0) # MyClass classmethod. 0
MyClass.call_original_method() # MyClass classmethod. 5
MySubclass.method(0) # MySubclass classmethod. 0
MySubclass.call_original_method() # MySubclass classmethod. 6
# Викликаємо методи класу через об'єкт.
my_obj = MyClass()
my_obj.method(1)
my_obj.call_class_method()
Будь-яка функція може використовуватися як декоратор.
Заміна сеттерів і геттеров
З допомогою класу property призначаються геттери, сетери, делеттеры.
class property([fget[, fset[, fdel[, doc]]]])
Геттери і сетери в TypeScript представлені в такому вигляді:
Здійснюється передача параметрів в клас Python. Декоратор property має методи:
- fget - отримує значення атрибута;
- fset визначає значення атрибута;
- fdel видаляє;
- doc створює опис атрибута. Якщо doc не призначений, повертається копія опис fget(), якщо є.
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "i'm the 'x' property.")
Використання функції як декоратор Pythonproperty:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
Property створив функції x декоратор. Оскільки у всіх декораторів є вбудовані методи setter, getter, deletter, можна викликати один з них.
Особливості
При роботі з декоратором необхідно враховувати деякі особливості:
- Використання декораторів трохи уповільнює виклик функції.
- Один раз задекорована функція не може бути раздекорированной. Існують способи обходу цього правила. Можна створити такий декоратор, який згодом можна буде від'єднати від функції. Але це не дуже вдала практика.
- З-за того, що декоратор обертає функцію, може ускладнитися налагодження. Проблема вирішується за допомогою модуля functools.
Модуль functools - збірник методів, завдяки яким забезпечується взаємодія з іншими функціями, він також є декоратором Python.
Корисний метод cmp_to_key(func) перетворює cmp() key(). Обидва методи призначені для сортування списку, але перший вилучений у "Пайтон 3.0", а другий доданий у версії 2. Lru_cache зберігає останні дзвінки в кеш. Якщо maxsixe вказаний як none, розмір кешу зростає нескінченно. Для зберігання часто використовуваних запитів використовується словник. Якщо аргумент typed=true, то аргументи різних типів кешується окремо. Відповідно, при typed=true, зберігаються в єдиний список.
Total_ordering декорують клас, який містить методи порівняння, і додає всі інші.
Partial(func, *args, **keywords) повертає функцію, яка викликається по першому аргументу з області параметрів методу, передає позиційний *args, який задається другим, і іменований kwargs.
Reduce працює так:
reduce(lambda x, y: x+y,[1, 2, 3, 4, 5])
Еквівалентно:
((((1+2)+3)+4)+5)
Reduce застосовує зазначену функцію послідовно пар елементів, зазначених у списку **keywords, або до всіх елементів *args. Таким чином, у наведеному вище прикладі з допомогою lambda function обробляються перші два елементи:
1+2
Далі результат підсумовується з третім, отриманий з цього результат додається до наступними елементами і т. д.
Update_wrapper оновлює оболонку так, щоб вона нагадувала обгорнуту функцію. В аргументах зазначаються ці дві функції, скопійовані і оновлювані атрибути.
assigned=WRAPPER_ASSIGNMENTS
Кортеж WRAPPER_ASSIGNMENTS містить за замовчуванням значення __name__, __module__, __annotations__ і __doc__.
updated=WRAPPER_UPDATES
У WRAPPER_UPDATES зазначаються атрибути, які оновлюються, за замовчуванням це __dict__.
Wraps викликає partial в якості декоратора.
Декоратор обробки помилки
Можливості декораторів дозволяють створювати таку функцію, яка при виникненні помилки видає один результат, якщо помилки немає - на іншу.
Реалізація:
import functools def retry(func): @functools.wraps(func) def wrapper(*args, **kwargs): while True: try: return func(*args, **kwargs) except Exception: pass return wrapper
Видно, що в разі винятків функція запускається заново.
@retry def do_something_unreliable(): if random.randint(010) > 1: raise IOError("Broken sauce, everything is hosed!!!111one") else: return "Awesome sauce!" print(do_something_unreliable())
Цей код означає, що 9 з 11 випадків виникне помилка.
def my_decorator(fn): def wrapped(): try: return fn() except as Exception e: print("Error:", e) return wrapped @my_decorator def my_func(): import random while True: if random.randint(0 4) == 0: raise Exception('Random!') print('Ok') my_func()
Тут в консоль виведеться:
Ok Ok Ok Error: Random!
Створення свого декоратора
Декоратор являє собою функцію, всередині якої знаходиться інша функція з відповідними методами.
Приклад декоратора в 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
У цьому коді декоратором є my_shiny_new_decorator(). Надалі декорує function_to_decorate() з області параметрів. the_wrapper_around_the_original_function - це приклад того, як буде оброблена декорируемая функція. В даному випадку додається:
print("Я - код, який відпрацює до виклику функції")
print("А я - код, що спрацьовує після")
My_shiny_new_decorator() повертає декоруєму the_wrapper_around_the_original_function().
Щоб відпрацювати яку-небудь функцію, вона обертається декоратором.
stand_alone_function = my_shiny_new_decorator(stand_alone_function)
В даному випадку декорована функція - stand_alone_function, декоратор - це my_shiny_new_decorator. Значення присвоюється змінній stand_alone_function.
def stand_alone_function():
print("Я проста самотня функція, адже ти не посмеешь мене змінювати?")
stand_alone_function()
Я - код, який відпрацює до виклику функції
Я проста самотня функція, адже ти не посмеешь мене змінювати?
А я - код, що спрацьовує після
Таким чином, видно, що stand_alone_function, яка виводила на екран одне речення, тепер виводить три пропозиції. Це здійснюється з допомогою декоратора.