обзор
В статье Deep Dive Into Python Decorators я представил концепцию декораторов Python, продемонстрировал множество классных декораторов и объяснил, как их использовать.
В этом уроке я покажу вам, как писать собственные декораторы. Как вы увидите, написание собственных декораторов дает вам много контроля и дает много возможностей. Без декораторов эти возможности потребовали бы много подверженных ошибкам и повторяющихся шаблонов, которые загромождают ваш код или полностью внешние механизмы, такие как генерация кода.
Краткий обзор, если вы ничего не знаете о декораторах. Декоратор — это вызываемый объект (функция, метод, класс или объект с методом call ()), который принимает вызываемый элемент в качестве входных данных и возвращает вызываемый элемент в качестве выходных данных. Как правило, возвращаемый вызываемый объект делает что-то до и / или после вызова входного вызываемого объекта. Вы применяете декоратор с помощью @
Привет Мир Декоратор
Давайте начнем с «Привет, мир!» декоратор. Этот декоратор полностью заменит любой оформленный вызываемый объект функцией, которая просто печатает «Hello World!».
python def hello_world(f): def decorated(*args, **kwargs): print 'Hello World!' return decorated
Вот и все. Давайте посмотрим на это в действии, а затем объясним различные части и как это работает. Предположим, у нас есть следующая функция, которая принимает два числа и печатает их произведение:
python def multiply(x, y): print x * y
Если вы вызываете, вы получаете то, что ожидаете:
multiply(6, 7) 42
Давайте украсим его с помощью нашего декоратора hello_world, пометив функцию умножения @hello_world
.
python @hello_world def multiply(x, y): print x * y
Теперь, когда вы вызываете умножение с любыми аргументами (включая неправильные типы данных или неправильное количество аргументов), результатом всегда будет «Hello World!» распечатаны.
« `python multiply (6, 7) Hello World!
multiply () Привет, мир!
умножить (‘zzz’) Hello World! « `
OK. Как это работает? Оригинальная функция умножения была полностью заменена вложенной декорированной функцией внутри декоратора hello_world . Если мы проанализируем структуру декоратора hello_world, то вы увидите, что он принимает входной вызываемый элемент f (который не используется в этом простом декораторе), он определяет вложенную функцию с именем decor, которая принимает любую комбинацию аргументов и аргументов ключевого слова ( def decorated(*args, **kwargs)
), и, наконец, он возвращает украшенную функцию.
Написание функций и методов декораторов
Нет разницы между написанием функции и метода декоратора. Определение декоратора будет таким же. Вызываемый вход будет либо обычной функцией, либо связанным методом.
Давайте проверим это. Вот декоратор, который просто печатает вызываемый ввод и печатает перед его вызовом. Это очень типично для декоратора, который выполняет какое-то действие и продолжает, вызывая исходный вызываемый объект.
python def print_callable(f): def decorated(*args, **kwargs): print f, type(f) return f(*args, **kwargs) return decorated
Обратите внимание на последнюю строку, которая вызывает входной вызов вызываемым образом и возвращает результат. Этот декоратор ненавязчив в том смысле, что вы можете декорировать любую функцию или метод в рабочем приложении, и приложение будет продолжать работать, потому что декорированная функция вызывает оригинал и просто имеет небольшой побочный эффект.
Давайте посмотрим на это в действии. Я украслю нашу функцию умножения и метод.
« `python @print_callable def multiply (x, y): print x * y
класс A (объект): @print_callable def foo (self): выведите ‘foo () здесь’ « `
Когда мы вызываем функцию и метод, вызываемый объект печатается, а затем они выполняют свою первоначальную задачу:
« `python multiply (6, 7) <функция multiply в 0x103cb6398> <тип ‘function’> 42
A (). Foo () <function foo at 0x103cb6410> <тип ‘function’> foo () здесь « `
Декораторы с аргументами
Декораторы тоже могут принимать аргументы. Эта возможность настроить работу декоратора очень мощна и позволяет использовать один и тот же декоратор во многих контекстах.
Предположим, ваш код слишком быстрый, и ваш начальник просит вас немного его замедлить, потому что вы заставляете других членов команды выглядеть плохо. Давайте напишем декоратор, который измеряет, как долго работает функция, и если она выполняется менее чем за определенное количество секунд t , она будет ждать, пока истечет t секунд, а затем вернется.
В настоящее время отличается то, что сам декоратор принимает аргумент t, который определяет минимальное время выполнения, и разные функции могут быть украшены разными минимальными временами выполнения. Также вы заметите, что при представлении аргументов декоратора требуются два уровня вложенности:
« `время импорта Python
def minimal_runtime (t): def украшенный (f): def wrapper ( args, ** kwargs): start = time.time () result = f ( args, ** kwargs) runtime = time.time () — запустить, если время выполнения <t: time.sleep (t — время выполнения) возвращаемый результат возвращаемая оболочка возвращаемая оформленная « `
Давайте распакуем это. Сам декоратор — функция Minimum_runtime принимает аргумент t , который представляет минимальное время выполнения для оформленного вызываемого объекта. Входной вызываемый f был «передан» во вложенную декорированную функцию, а входные вызываемые аргументы «переданы» в еще одну оболочку вложенной функции.
Фактическая логика происходит внутри функции оболочки . Время начала записывается, исходный вызываемый f вызывается с его аргументами, а результат сохраняется. Затем проверяется время выполнения, и если оно меньше минимального t, то оно спит в течение остального времени, а затем возвращается.
Чтобы проверить это, я создам пару функций, которые вызывают умножение, и украсим их с разными задержками.
« `python @minimum_runtime (1) def slow_multiply (x, y): умножить (x, y)
@minimum_runtime (3) def slower_multiply (x, y): multiply (x, y) « `
Теперь я вызову умножение напрямую, а также более медленные функции и буду измерять время.
« `время импорта Python
funcs = [multiply, slow_multiply, slower_multiply] для f в funcs: start = time.time () f (6, 7) print f, time.time () — start « `
Вот вывод:
plain 42 <function multiply at 0x103cb6b90> 1.59740447998e-05 42 <function wrapper at 0x103d0bcf8> 1.00477004051 42 <function wrapper at 0x103cb6ed8> 3.00489807129
Как вы можете видеть, первоначальное умножение почти не заняло времени, а более медленные версии были действительно отложены в соответствии с предоставленным минимальным временем выполнения.
Еще один интересный факт заключается в том, что выполняемая декорированная функция является оберткой, что имеет смысл, если вы следуете определению декорированной. Но это может быть проблемой, особенно если мы имеем дело с декораторами стека. Причина в том, что многие декораторы также проверяют свои входные данные и проверяют их имя, подпись и аргументы. В следующих разделах будет рассмотрен этот вопрос и предоставлены советы по передовому опыту.
Декораторы объектов
Вы также можете использовать объекты в качестве декораторов или возвращать объекты из ваших декораторов. Единственное требование состоит в том, что у них есть метод __call __ () , поэтому они могут быть вызваны. Вот пример для объектного декоратора, который подсчитывает, сколько раз вызывается его целевая функция:
python class Counter(object): def __init__(self, f): self.f = f self.called = 0 def __call__(self, *args, **kwargs): self.called += 1 return self.f(*args, **kwargs)
Вот оно в действии:
« `python @Counter def bbb (): print ‘bbb’
BBB () BBB
BBB () BBB
BBB () BBB
печать bbb.called 3 « `
Выбор между функциональными и объектными декораторами
В основном это вопрос личных предпочтений. Вложенные функции и замыкания функций обеспечивают все управление состоянием, которое предлагают объекты. Некоторые люди чувствуют себя как дома с классами и предметами.
В следующем разделе я расскажу о хороших поведенческих декораторах, а объектно-ориентированные декораторы требуют немного дополнительной работы, чтобы быть хорошими.
Хорошо ведущие себя декораторы
Декораторы общего назначения часто могут быть сложены. Например:
python @decorator_1 @decorator_2 def foo(): print 'foo() here'
При наложении декораторов внешний декоратор (в данном случае decorator_1) получит запрос, возвращаемый внутренним декоратором (decorator_2). Если decorator_1 каким-то образом зависит от имени, аргументов или строки документа исходной функции, а decorator_2 реализован наивно, то decorator_2 увидит, что не увидит правильную информацию из исходной функции, а только вызываемый элемент, возвращаемый decorator_2.
Например, вот декоратор, который проверяет имя своей целевой функции в нижнем регистре:
python def check_lowercase(f): def decorated(*args, **kwargs): assert f.func_name == f.func_name.lower() f(*args, **kwargs) return decorated
Давайте украсим функцию этим:
python @check_lowercase def Foo(): print 'Foo() here'
Вызов Foo () приводит к утверждению:
« `plain В [51]: Foo () ————————————————————————— AssertionError Traceback (последний вызов был последним)