Функції в Python - об'єкти
Для розуміння декораторів, спочатку треба усвідомити, що всі функції в Python це об'єкти. Що тягне за собою ряд наслідків. Розглянемо їх на простому прикладі:
Отже, запам'ятайте цей приклад, ми до нього ще повернемося. Інша особливість функцій в Python це те, що вони можуть бути оголошені всередині іншої функції!
Посилання на функції
Ще не заплуталися? А тепер найцікавіша частина. Так як функції це ні що інше як об'єкти, то отже:
- їх можна призначати змінним
- вони можуть бути оголошені всередині іншої функції
Тобто функція може повернути іншу функцію в якості результату:
Стоп! Це ще не все! Якщо можна повернути функцію, то і передати в якості параметра її теж можна.
Ось в цілому і все, що треба знати про декоратора. Тобто, декоратори це щось на зразок оболонки. Вони дозволяють виконувати код до і після функції, яку вони оточують без зміни самої функції.
саморобні декоратори
Як зробити їх вручну?
Тепер, ви напевно зробите так, щоб кожен раз при виконанні функції a_stand_alone_function. замість неї відпрацьовувала функція a_stand_alone_function_decorated. Все досить просто, просто поверніть функцію my_shiny_new_decorator з a_stand_alone_function.
викриття декораторів
У попередньому прикладі використаний наступний синтаксис декоратора:
Декоратори це версія патерну проектування Декоратор для Python. Взагалі в Python включено кілька класичних патернів для полегшення процесу розробки, наприклад ітератори.
Звичайно можна накопичувати декоратори:
Використовуємо синтаксис декораторів в Python:
Порядок вказівки декораторів має значення:
Нарешті відповідь на питання
У висновку ви бачите відповідь на саме питання:
В принципі на цьому можна зупинитися, але якщо бажання ще не пропало дізнатися нове про декораторів, то читаємо далі.
Передача параметрів в функції оточені декоратором
Застосування декораторів до методів
Як можна було вже здогадатися, по-суті, методи і функції це одне і теж в Python, за винятком того, що методи повинні отримати посилання на батьківський об'єкт в якості першого параметра (self). Це означає, що декоратори для них будуються абсолютно так само. Просто не забувайте про self:
Звичайно при створенні глобального декоратора, який буде застосований для всіх функцій і методів, потрібно використовувати * args і ** kwargs.
Передача параметрів декораторам
Відмінно, а що скажете на тему передачі аргументів самому декораторові? Тут є невелика хитрість. По суті декоратор приймає як параметр тільки функцію і ви не можете передати аргументи функції оточеній декоратором безпосередньо.
Перед тим як показати вам рішення проблеми, давайте розглянемо приклад:
Нічого не змінилось. Відбувається виклик my_decorator. Отже, коли ви указивет @my_decorator ви говорите Python викликати функцію цього декоратора.
Нічого дивного. Проробимо все те ж саме, але припустимо допоміжні змінні:
Помітили? Ми використовуємо виклик функції з синтаксисом @. Повернемося до декораторам з аргументами. Якщо ми застосовуємо функції для генерації декораторів на ходу, то можемо і передавати аргументи цієї функції, чи не так?
Ось вам і декоратор з аргументами, які можна задати за допомогою змінних.
Як бачите, будь-якого декораторові можна передати параметри при такому підході. Нічого не заважає використовувати * args і ** kwargs. Але пам'ятайте, що декоратори викликаються тільки один раз. Тільки коли Python імпортує цей скрипт. Тобто динамічно встановити значення цих змінних ви не зможете. Коли ви імпортуєте скрипт, функція вже використовує декоратор, тому поміняти щось в подальшому не вийде.
Ще приклад: декоратор декоруючий декоратор
Ось такий от бонус. Наведу сниппет коду, щоб будь-який декоратор брав будь-який параметр. У підсумку, для передачі аргументів, ми створили наш декоратор за допомогою допоміжної функції. Давайте напишемо декоратор для декоратора:
Приклад його використання:
Так-так, все це виглядає приблизно наступним чином: "Щоб зрозуміти рекурсію, спочатку треба зрозуміти рекурсію". Але в підсумку хіба немає почуття, що ви освоїли щось нове і цікаве?
Правильне застосування декораторів
- Декоратори уповільнюють роботу функцій. Не забувайте про це.
- Зняти декоратор з функції неможливо. Звичайно існує приклади як це зробити, але так ніхто не чинить. Так що якщо ви застосували декоратор, то з ним доведеться працювати протягом усього коду.
- Декоратори оточують функції, що ускладнює їх налагодження.
Python 2.5 і вище представляє рішення останньої проблеми. Він включає в себе модуль functools (functools.wraps), який копіює назву, модуль і документацію кожної задекоріованной функції. Найцікавіше - functools.wraps і є декоратор.
Коли варто застосовувати декоратори?
Головне питання - а коли, власне, застосовувати декоратори? Звичайно виглядають вони відмінно, але де ж їх використовувати? Можу навести хоч 1000 прикладів. Класичний приклад - додавання функціоналу для функції з сторонньої бібліотеки (яку ви не можете правити). Ви можете застосовувати декоратори для розширення декількох функцій, щоб не переписувати їх кожен раз. Це позбавляє від зайвого коду:
Найцікавіше, що ви можете використовувати декоратори практично миттєво, без переписування існуючого коду: