Шаг 156.
Python: сборник рецептов.
Метапрограммирование. Создание обертки для функции

    На этом шаге мы рассмотрим особенности использования декораторов.

Задача

    Вы хотите обернуть функцию слоем, добавляющим дополнительную логику (например, логирование, профилирование и т. п.).

Решение

    Если вам потребуется обернуть функцию дополнительным кодом, определите функцию-декоратор. Например:

>>> import time
>>> from functools import wraps
>>> def timethis(func):
    '''
        Декоратор, который выводит время выполнения.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return r
    return wrapper

>>> 

    Вот пример использования декоратора:

>>> @timethis
def countdown(n):
	'''
	Counts down
	'''
	while n > 0:
		n -= 1

		
>>> countdown(100000)
0.022107839584350586
>>> countdown(10000000)
0.9561281204223633
>>> 


Обсуждение

    Декоратор - это функция, которая принимает функцию на вход и возвращает новую функцию на выходе. Когда вы пишете такой код:

@timethis
def countdown(n):
    .  .  .
это равноценно такой последовательности шагов:
def countdown(n):
    .  .  .

countdown = timethis(countdown)

    Встроенные декораторы @staticmethod, @classmethod и @property работают так же. Например, два этих фрагмента эквивалентны:

class A:
    @classmethod 
    def method(cls): 
        pass

class B:
    # Эквивалентное определение метода класса 
    def method(cls): 
        pass
    
    method = classmethod(method)

    Код внутри декоратора обычно создает новую функцию, которая принимает любые аргументы через *args и **kwargs, как в функции wrapper() в этом рецепте. Внутри этой функции вы помещаете вызов изначальной входящей функции и возвращаете ее результат. Однако вы также добавляете дополнительный код по желанию (например, профилирующий). Созданная функция wrapper возвращается в результате и занимает место изначальной функции.

    Чрезвычайно важно подчеркнуть, что декораторы в общем случае не изменяют сигнатуру вызова или возвращаемое значение декорируемой функции. Использование *args и **kwargs здесь позволяет убедиться, что могут быть приняты любые входные аргументы. Возвращаемое значение декоратора практически всегда будет результатом вызова func(*args, **kwargs), где func - это изначальная недекорированная функция.

    При первой встрече с декораторами обычно начинают с простых примеров вроде показанного выше. Однако если вы будете писать декораторы в реальной работе, то придется помнить о нескольких тонкостях. Например, об использовании декоратора @wraps(func) в показанном решении легко забыть, но именно оно обеспечивает сохранение метаданных функции (описано в следующем рецепте). Следующие несколько рецептов касаются тонкостей, связанных с написанием декораторов.

    На следующем шаге мы рассмотрим сохранение метаданных функции при написании декораторов.




Предыдущий шаг Содержание Следующий шаг