Недавно у меня была возможность перечитать этот пост. Это не было приятным опытом — мне было ясно, что попытка не удалась.
Эта неудача — и две другие вещи — побудили меня попробовать еще раз.
- Мэтт Харрисон опубликовал отличную электронную книгу « Руководство по: изучению декораторов Python» .
- Теперь у меня есть теория о том, почему большинство объяснений декораторов (включая мое) терпят неудачу, и некоторые идеи о том, как лучше структурировать введение в декораторы.
Существует старая поговорка о том, что «у каждой палки есть два конца, один из которых ее можно взять, а другой — нет». Я полагаю, что большинство объяснений декораторов терпят неудачу, потому что они поднимают палку не с того конца.
В этом посте я покажу вам, как выглядит неправильный конец флешки, и укажу, почему я думаю, что это неправильно. И я покажу вам, как мне кажется, правый конец палки.
Неправильный способ объяснить декораторы
Большинство объяснений Python-декораторов начинаются с примера декорируемой функции, например:
def aFunction(): print("inside aFunction")
и затем добавьте строку декоратора, которая начинается со знака @:
@myDecorator def aFunction(): print("inside aFunction")
На этом этапе автор введения часто определяет декоратор как строку кода, которая начинается с «@». (В моем предыдущем посте я назвал такие строки «аннотациями».)
Например, в 2008 году Брюс Экель написал в своем блоге Artima
Декоратор функции применяется к определению функции, помещая его в строку перед началом определения функции.
и в 2004 году Филипп Эби написал в статье в журнале доктора Добба
Декораторы могут появляться перед любым определением функции…. Вы можете даже разместить несколько декораторов в одном определении функции, по одному на строку.
Теперь в этом подходе к объяснению декораторов есть две ошибки. Во-первых, объяснение начинается не в том месте. Он начинается с примера декорируемой функции и строки аннотации, когда он должен начинаться с самого декоратора. Объяснение должно заканчиваться, а не начинаться с декорированной функции и строки аннотации. Строка аннотации, в конце концов, просто синтаксический сахар — вовсе не является существенным элементом в концепции декоратора.
Во-вторых, термин «декоратор» используется неправильно (или неоднозначно) для обозначения как декоратора, так и строки аннотации. Например, в своей статье в журнале доктора Добба , после использования термина «декоратор» для обозначения строки аннотации, Филипп Эби продолжает определять «декоратор» как вызываемый объект.
Но прежде чем вы сможете это сделать, вам нужно иметь несколько декораторов для стека. Декоратор — это вызываемый объект (например, функция), который принимает один аргумент — декорируемую функцию.
Итак … может показаться, что декоратор — это и вызываемый объект (например, функция), и одна строка кода, которая может появиться перед строкой кода, которая начинает определение функции. Это все равно, что сказать, что «адрес» — это и здание (или квартира) в определенном месте, и набор линий (написанных карандашом или чернилами) на лицевой стороне почтового конверта. Неоднозначность может быть почти невидимой для кого-то, знакомого с декораторами, но это очень сбивает с толку читателя, который пытается узнать о декораторах с нуля.
Правильный способ объяснить декораторы
Так как же нам объяснить декораторы?
Ну, мы начинаем с декоратора, а не с функции, которая будет украшена.
Один
Начнут с основным понятием функции — функция является то , что генерирует значение на основе значений своих аргументов.
Два
Отметим, что в Python функции являются объектами первого класса, поэтому их можно передавать как другие значения (строки, целые числа, объекты и т. Д.).
Три
Мы отмечаем , что , поскольку функции являются объектами первого класса в Python, мы можем написать функции, как (а) принимают объекты функции в качестве значений аргумента, и (б) Функция возврата объектов в качестве возвращаемых значений. Например, вот функция foobar, которая принимает объект функции original_function в качестве аргумента и возвращает объект функции new_function в результате.
def foobar(original_function): # make a new function def new_function(): # some code return new_function
Четыре
Мы определяем «декоратор».
Декоратора представляет собой функцию (например, Foobar в приведенном выше примере) , который принимает объект функции в качестве аргумента, и возвращает объект функции в качестве возвращаемого значения.
Так что у нас это есть — определение декоратора. Все, что мы говорим о декораторах, является уточнением, расширением или дополнением к этому определению декоратора.
Пять
Мы покажем, как выглядят внутренности декоратора. В частности, мы показываем различные способы, которыми декоратор может использовать функцию original_function при создании функции new_function . Вот простой пример.
def verbose(original_function): # make a new function that prints a message when original_function starts and finishes def new_function(*args, **kwargs): print("Entering", original_function.__name__) original_function(*args, **kwargs) print("Exiting ", original_function.__name__) return new_function
Шесть
Мы покажем, как вызвать декоратор — как мы можем передать в декоратор один функциональный объект (его вход) и получить от него другой функциональный объект (его выход). В следующем примере мы передаем функциональный объект widget_func подробному декоратору и возвращаем новый функциональный объект, которому присваиваем имя talkative_widget_func .
def widget_func(): # some code talkative_widget_func = verbose(widget_func)
Семь
Мы отмечаем, что декораторы часто используются для добавления функций к функции original_function. Точнее говоря, декораторы часто используются для создания new_function , которая примерно выполняет то, что делает original_function , но также делает вещи в дополнение к тому, что делает original_function .
И отметим, что выходные данные декоратора обычно используются для замены исходной функции, которую мы передали декоратору в качестве аргумента. Типичное использование декораторов выглядит следующим образом. (Обратите внимание на изменение в строке 4 из предыдущего примера.)
def widget_func(): # some code widget_func = verbose(widget_func)
Таким образом, для всех практических целей при типичном использовании декоратора мы пропускаем функцию ( widget_func ) через декоратор ( многословно ) и возвращаем расширенную (или расширенную, или «украшенную») версию функции.
Восемь
Мы вводим «синтаксис декоратора» Python, который использует «@» для создания строк аннотации. Эта особенность в основном является синтаксическим сахаром, который позволяет переписать наш последний пример следующим образом:
@verbose def widget_func(): # some code
Результат этого примера точно такой же, как и в предыдущем примере — после его выполнения у нас есть widget_func, который имеет все функциональные возможности исходного widget_func плюс функциональность, которая была добавлена многословным декоратором.
Обратите внимание, что при таком объяснении декораторов синтаксис «@» и аннотации — это одна из последних вещей, которые мы вводим, а не одна из первых.
И мы абсолютно не называем строку 1 «декоратором». Мы можем называть строку 1, скажем, «строкой декоратора» или «строкой вызова декоратора» или «строкой аннотации»… что угодно. Но строка 1 не является «декоратором». Строка 1 — это строка кода. Декоратор — это функция — совсем другое животное.
Девять
После того, как мы изобрели эти основы, есть несколько расширенных функций, которые необходимо рассмотреть
- Мы объясняем, что декоратор не обязательно должен быть функцией (это может быть любой вызываемый тип, например, класс).
- Мы объясняем, как декораторы могут быть вложены в другие декораторы.
- Мы объясняем, как
линии декоратора могут быть «сложены». Лучший способ выразиться так: мы объясняем, как декораторы могут быть «прикованы».
декораторов
- Мы объясняем, как дополнительные аргументы могут быть переданы декораторам, и как декораторы могут их использовать.
Десять — поваренная книга декораторов
Материал, который мы рассмотрели до этого момента, — это то, что будет рассказывать любое базовое введение в декораторы Python. Но программисту на Python нужно что-то большее, чтобы работать с декораторами. Ему (или ей) нужен каталог рецептов, шаблонов, примеров и комментариев, который описывает / показывает / объясняет, когда и как можно использовать декораторы для выполнения конкретных задач. (В идеале, такой каталог также должен включать примеры и предупреждения о проблемах декоратора и антишаблонах.) Такой каталог можно назвать «Поваренная книга Python Decorator» или, возможно, «Шаблоны Python Decorator».
-
Насколько я знаю, такой поваренной книги в настоящее время не существует.
-
Библиотека Python Decorator на Python wiki — это коллекция примеров декораторов. Он имеет свое применение, но у него нет систематической организации и пояснительного материала настоящей кулинарной книги.
-
Нечто подобное поваренной книге дескриптора, хотя и систематически не организованное, может быть сгенерировано путем поиска в поваренной книге Python ActiveState с фильтрацией по «дескриптору».
Ну это все. Я описал то, что я считаю неправильным (ну, скажем, неоптимальным) в большинстве представлений декораторам. И я набросал то, что я считаю лучшим способом структурировать введение в декораторы.
Теперь я могу объяснить, почему мне нравится электронная книга Мэтта Харрисона » Руководство по: изучению декораторов Python» . Введение Мэтта структурировано таким образом, что я думаю, что введение в декораторы должно быть структурировано. Подбирает палку к правильному концу.
Первые две трети Руководства почти не говорят о декораторах. Вместо этого Мэтт начинает с подробного обсуждения работы функций Python. К тому времени, когда дискуссия доходит до декораторов, мы уже хорошо понимаем внутреннюю механику функций. И поскольку большинство декораторов являются функциями (помните наше определение декоратора ), в этот момент Мэтту относительно легко объяснить внутреннюю механику декораторов.
Что так и должно быть.