Декораторы без аргументов#

Декоратор в Python это функция, которая используется для изменения функции, метода или класса. Декораторы используются для добавления какого-то функционала к функциям/классам.

Синтаксис декоратора - это синтаксический сахар, эти два определения функций эквивалентны:

def f(...):
    ...
f = verbose(f)

@verbose
def f(...):
    ...

Например, допустим, есть ряд функций к которым надо добавить print с информацией о том какая функция вызывается:

def upper(string):
    return string.upper()

def lower(string):
    return string.lower()

def capitalize(string):
    return string.capitalize()

Самый базовый вариант будет вручную добавить строку в каждой функции:

def upper(string):
    print('Вызываю функцию upper')
    return string.upper()

def lower(string):
    print('Вызываю функцию lower')
    return string.lower()

def capitalize(string):
    print('Вызываю функцию capitalize')
    return string.capitalize()

Однако в этом случае будет очень много повторений, а главное, при необходимости, например, заменить print на logging или просто изменить сообщение придется редактировать большое количество функций. Вместо этого можно создать одну функцию, которая перед вызовом исходной функции будет выводить сообщение:

def verbose(func):
    def wrapper(*args, **kwargs):
        print(f'Вызываю функцию {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

Функция verbose принимает как аргумент функцию, а затем возвращает внутреннюю функцю wrapper внутри которой выводится сообщение, а затем вызывается исходная функция. Для того чтобы функция verbose работала надо заменить функцию upper внутренней функцией wrapper таким образом:

In [10]: upper = verbose(upper)

Теперь при вызове функции upper, вызывается внутренняя функция wrapper и перед вызовом самой upper выводится сообщение:

In [12]: upper(s)
Вызываю функцию upper
Out[12]: 'LINE'

К сожалению, в этом случае надо после определения каждой функции добавлять строку для модификации ее поведения:

def verbose(func):
    def wrapper(*args, **kwargs):
        print(f'Вызываю функцию {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

def upper(string):
    return string.upper()
upper = verbose(upper)

def lower(string):
    return string.lower()
lower = verbose(lower)

def capitalize(string):
    return string.capitalize()
capitalize = verbose(capitalize)

Так как показанный выше синтаксис не очень удобен, в Python есть другой синтаксис, который позволяет сделать то же самое более компактно:

def verbose(func):
    def wrapper(*args, **kwargs):
        print(f'Вызываю функцию {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

@verbose
def upper(string):
    return string.upper()

@verbose
def lower(string):
    return string.lower()

@verbose
def capitalize(string):
    return string.capitalize()

При использовании декораторов, информация исходной функции заменяется внутренней функцией декоратора:

In [2]: lower
Out[2]: <function __main__.verbose.<locals>.wrapper(*args, **kwargs)>

In [4]: lower?
Signature: lower(*args, **kwargs)
Docstring: <no docstring>
File:      ~/repos/experiments/netdev_try/<ipython-input-1-32089045b87b>
Type:      function

Чтобы исправить это необходимо воспользоваться декоратором wraps из модуля functools:

from functools import wraps

def verbose(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f'Вызываю функцию {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

@verbose
def upper(string):
    return string.upper()

@verbose
def lower(string):
    return string.lower()

@verbose
def capitalize(string):
    return string.capitalize()

In [7]: lower
Out[7]: <function __main__.lower(string)>

In [8]: lower?
Signature: lower(string)
Docstring: <no docstring>
File:      ~/repos/experiments/netdev_try/<ipython-input-6-13e6266ce16f>
Type:      function

Декоратор wraps переносит информацию исходной функции на внутреннюю и хотя это можно сделать и вручную, лучше пользоваться wraps.