Функция, украшенная с помощью functools.wraps, вызывает TypeError с именем оболочки. Зачем? Как избежать?

def decorated(f):
    @functools.wraps(f)
    def wrapper():
        return f()
    return wrapper

@decorated
def g():
    pass

functools.wraps выполняет свою работу по сохранению имени g:

>>> g.__name__
'g'

Но если я передаю аргумент g, я получаю a TypeError, содержащий имя обертки:

>>> g(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: wrapper() takes no arguments (1 given)

Откуда это имя? Где он сохранился? И есть ли способ сделать исключение похожим на g() takes no arguments?

Ответ 1

Название происходит от объекта кода; как функция, так и объект кода (содержащий исполняемый байт-код, среди прочих) содержат это имя:

>>> g.__name__
'g'
>>> g.__code__.co_name
'wrapper'

Атрибут объекта кода доступен только для чтения:

>>> g.__code__.co_name = 'g'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

Вам нужно будет создать целый новый объект кода, чтобы переименовать его, см. предыдущий мой ответ, где я определил функцию для этого; используя функцию rename_code_object() на вашей украшенной функции:

>>> g = rename_code_object(g, 'g')
>>> g(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: g() takes no arguments (1 given)

Обратите внимание, однако, что это полностью замаскирует, какой код запускался! Обычно вы хотите видеть, что была задействована обертка декоратора; это оболочка, которая выдает исключение, а не оригинальную функцию.