Сообщить об ошибке.

Сохранение атрибута __name__ для декорируемой функции

Большим удобством при работе с Python, особенно в интерактивной оболочке, является его мощная способность к самоанализу. Интроспекция - это способность объекта узнавать о своих собственных атрибутах во время выполнения. Например, функция знает свое собственное имя и документацию:

>>> print
# <built-in function print>
>>> print.__name__
# 'print'

>>> help(print)
# Help on built-in function print in module builtins:
# print(...)
#     print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
#     Prints the values to a stream, or to sys.stdout by default.
#     Optional keyword arguments:
#     file:  a file-like object (stream); defaults to the current sys.stdout.
#     sep:   string inserted between values, default a space.
#     end:   string appended after the last value, default a newline.
#     flush: whether to forcibly flush the stream.

Интроспекция работает и для функций, которые вы сами определяете. Для примера будем использовать файл decorator.py из материала "Возврат значений из декорируемой функции":

from decorator import talk

@talk
def say(name):
    """Функция печатает приветствие
    name: строка с именем для приветствия
    """
    return f'Привет {name}.'

>>> say
# <function talk.<locals>.wrapper at 0x7fd696ce18c8>
>>> say.__name__
# 'wrapper'
>>> help(say)
# Help on function wrapper in module decorator:
# wrapper(*args, **kwargs)
# (END)

После того, как к функции say() применен декоратор, её атрибут __name__ изменился на имя внутренней функции wrapper() декоратора @talk. Хотя технически это верно, но это не очень полезная информация для IDE и процесса отладки программы .

Чтобы предотвратить смену атрибута __name__ и документации декорируемой функции, декораторы должны использовать декоратор @functools.wraps, который сохранит информацию о первоначальной функции. Изменим декоратор, сохраненный в файле decorator.py следующим образом:

import functools

def talk(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        dash = '-'*15
        string = func(*args, **kwargs)
        return dash + '\n' + string + '\n' + dash
    return wrapper

Декорируемая функция say() остается без изменений. Мы знаем, что операторы и функций определенные в модуле инициализируются в момент импорта модуля и выполняются только один раз, следовательно, что бы изменения, сделанные в файле модуля декоратора decorator.py вступили в силу, нужно выйти и снова зайти в интерактивный режим и произвести все операции заново или перезагрузить модуль декоратора не выходя из интерактивного режима и переопределить функцию say() с перезагруженным декоратором @talk.

# перезагрузка декоратора
import importlib, sys
talk = importlib.reload(sys.modules['decorator']).talk

# переопределение функции
@talk
def say(name):
    """Функция печатает приветствие
    name: строка с именем для приветствия
    """
    return f'Привет {name}.'

# пробуем
>>> say
# <function say at 0x7fbc486b8158>
>>> say.__name__
# 'say'
>>> help(say)
# Help on function say in module __main__:
# say(name)
#     Функция печатает приветствие
#     name: строка с именем для приветствия
# (END)

Теперь у функции say() атрибут __name__ и документация не теряются после применения декоратора.

Декоратор @functools.wraps использует функцию functools.update_wrapper() для обновления специальных атрибутов __name__ и __doc__.