В комментарии к этому ответ на другой вопрос, кто-то сказал, что не уверен, что делает functools.wraps
. Итак, я задаю этот вопрос, чтобы в StackOverflow была запись об этом для дальнейшего использования: что именно делает functools.wraps
?
Что делает functools.wraps?
Ответы (6)
Когда вы используете декоратор, вы заменяете одну функцию другой. Другими словами, если у вас есть декоратор
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
тогда, когда ты скажешь
@logged
def f(x):
"""does some math"""
return x + x * x
это в точности то же самое, что и сказать
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
и ваша функция f
заменяется функцией with_logging
. К сожалению, это означает, что если вы затем скажете
print(f.__name__)
он напечатает with_logging
, потому что это имя вашей новой функции. Фактически, если вы посмотрите на строку документации для f
, она будет пустой, потому что with_logging
не имеет строки документации, и поэтому написанная вами строка документации больше не будет там. Кроме того, если вы посмотрите на результат pydoc для этой функции, он не будет указан как принимающий один аргумент x
; вместо этого он будет указан как принимающий *args
и **kwargs
, потому что это то, что принимает with_logging.
Если бы использование декоратора всегда означало потерю этой информации о функции, это было бы серьезной проблемой. Вот почему у нас есть functools.wraps
. Это берет функцию, используемую в декораторе, и добавляет функциональность копирования имени функции, строки документации, списка аргументов и т. Д. А поскольку wraps
сам является декоратором, следующий код работает правильно:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wraps
для этой работы, разве это не должно быть просто частью шаблона декоратора? когда бы вы не хотели использовать @wraps?
- person wim; 02.01.2014
@wraps
, чтобы выполнять различные типы модификации или аннотации к скопированным значениям. По сути, это расширение философии Python, согласно которой явное лучше, чем неявное, а особые случаи недостаточно особенные, чтобы нарушать правила. (Код намного проще, а язык легче понять, если @wraps
необходимо вводить вручную, а не использовать какой-то специальный механизм отказа.)
- person ssokolow; 30.03.2014
special cases aren't special enough to break the rules
: если wraps
было поведением по умолчанию, это будет правилом. wraps
для меня больше похоже на патч, чем на более явный способ решения задач. Тем не менее, лучше вместо изменения существующих правил добавить патч и создать Python 4 с партером из 5 пользователей.
- person Marco Sulla; 22.07.2015
Я очень часто использую в качестве декораторов классы, а не функции. У меня были некоторые проблемы с этим, потому что объект не будет иметь всех тех атрибутов, которые ожидаются от функции. Например, у объекта не будет атрибута __name__
. У меня была конкретная проблема с этим, которую было довольно сложно отследить, где Django сообщал об ошибке «объект не имеет атрибута '__name__
'». К сожалению, для декораторов в стиле классов я не верю, что @wrap справится со своей задачей. Вместо этого я создал базовый класс декоратора следующим образом:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
Этот класс передает все вызовы атрибутов декорируемой функции. Итак, теперь вы можете создать простой декоратор, который проверяет, указаны ли два аргумента следующим образом:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
@wraps
, @wraps
- это просто вспомогательная функция для functools.update_wrapper()
. В случае декоратора класса вы можете вызвать update_wrapper()
прямо из вашего __init__()
метода. Итак, вам вообще не нужно создавать DecBase
, вы можете просто включить __init__()
из process_login
строку: update_wrapper(self, func)
. Это все.
- person Fabiano; 14.02.2019
Flask
с его add_url_route
требует (в некоторых случаях?), Чтобы предоставленная функция view_func
имела __name__
, что уже не так, если предоставленная функция на самом деле является декорированным методом, даже когда functools.wraps
используется в декораторе.
- person Joël; 04.10.2020
update_wrapper
вместо @wraps
делает свою работу :)
- person Joël; 04.10.2020
Начиная с Python 3.5+:
@functools.wraps(f)
def g():
pass
Псевдоним для g = functools.update_wrapper(g, f)
. Он делает ровно три вещи:
- он копирует атрибуты
__module__
,__name__
,__qualname__
,__doc__
и__annotations__
изf
наg
. Этот список по умолчанию находится вWRAPPER_ASSIGNMENTS
, вы можете увидеть его в источнике functools а>. - он обновляет
__dict__
изg
всеми элементами изf.__dict__
. (см.WRAPPER_UPDATES
в источнике) - он устанавливает новый атрибут
__wrapped__=f
наg
Следствием этого является то, что g
имеет то же имя, строку документации, имя модуля и подпись, что и f
. Единственная проблема в том, что в отношении подписи это не совсем так: просто inspect.signature
по умолчанию следует цепочкам оберток. Вы можете проверить это, используя inspect.signature(g, follow_wrapped=False)
, как описано в документе. Это имеет неприятные последствия:
- код оболочки будет выполняться, даже если предоставленные аргументы недействительны.
- код оболочки не может легко получить доступ к аргументу, используя его имя из полученных * args, ** kwargs. Действительно, нужно было бы обрабатывать все случаи (позиционные, ключевые слова, по умолчанию) и, следовательно, использовать что-то вроде
Signature.bind()
.
Теперь есть небольшая путаница между functools.wraps
и декораторами, потому что очень частый вариант использования при разработке декораторов - это обертывание функций. Но оба являются полностью независимыми концепциями. Если вам интересно понять разницу, я реализовал вспомогательные библиотеки для обоих: decopatch для записи декораторы, а также makefun, чтобы обеспечить замену @wraps
с сохранением подписи. Обратите внимание, что makefun
использует тот же проверенный прием, что и знаменитая библиотека decorator
.
Предпосылка: вы должны знать, как использовать декораторы, особенно с обертками. Этот комментарий объясняет это немного ясно или это ссылка также довольно хорошо это объясняет.
Каждый раз, когда мы используем For, например: @wraps, за которым следует наша собственная функция-оболочка. Согласно сведениям, приведенным в этой ссылке, говорится, что
functools.wraps - это удобная функция для вызова update_wrapper () в качестве декоратора функции при определении функции-оболочки.
Это эквивалентно частичному (update_wrapper, wrapped = wrapped, назначено = назначено, обновлено = обновлено).
Итак, декоратор @wraps фактически вызывает функцию functools.partial (func [, * args] [, ** keywords]).
Определение functools.partial () говорит, что
Частичный () используется для частичного приложения функции, которое «замораживает» некоторую часть аргументов функции и / или ключевых слов, в результате чего создается новый объект с упрощенной подписью. Например, partial () можно использовать для создания вызываемого объекта, который ведет себя как функция int (), где базовый аргумент по умолчанию равен двум:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
Это подводит меня к выводу, что @wraps вызывает частичную () и передает ей вашу функцию-оболочку в качестве параметра. В конце partial () возвращает упрощенную версию, то есть объект того, что находится внутри функции-оболочки, а не саму функцию-оболочку.
это исходный код оберток:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
Короче говоря, functools.wraps - это обычная функция. Давайте рассмотрим этот официальный пример. С помощью исходного кода мы можем см. более подробную информацию о реализации и выполняемых этапах следующим образом:
- wraps (f) возвращает объект, например O1. Это объект класса Partial strong >
- Следующий шаг - @ O1 ..., который является обозначением декоратора в Python. Это означает
wrapper = O1 .__ call __ (оболочка)