В комментарии к этому ответу на другой вопрос кто-то сказал, что они не уверены, что functools.wraps
делают. Итак, я задаю этот вопрос, чтобы в StackOverflow была запись об этом для дальнейшего использования: что именно делает functools.wraps
?
В комментарии к этому ответу на другой вопрос кто-то сказал, что они не уверены, что functools.wraps
делают. Итак, я задаю этот вопрос, чтобы в StackOverflow была запись об этом для дальнейшего использования: что именно делает functools.wraps
?
Когда вы используете декоратор, вы заменяете одну функцию другой. Другими словами, если у вас есть декоратор
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
; вместо этого он будет указан как take *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?@wraps
того, чтобы выполнять различные типы изменений или аннотаций для скопированных значений. По сути, это расширение философии Python, которое явное лучше, чем неявное, и особые случаи не достаточно особенные, чтобы нарушать правила. (Код гораздо проще, а язык легче понять, если его@wraps
нужно предоставить вручную, а не с помощью какого-либо специального механизма отказа.)Я очень часто использую классы, а не функции, для своих декораторов. У меня были некоторые проблемы с этим, потому что объект не будет иметь все те же атрибуты, которые ожидаются от функции. Например, у объекта не будет атрибута
__name__
. У меня была конкретная проблема с этим, которую было довольно трудно отследить, когда Django сообщал об ошибке «У объекта нет атрибута__name__
». К сожалению, для декораторов в стиле класса я не верю, что @wrap сделает эту работу. Вместо этого я создал базовый класс декоратора следующим образом:Этот класс передает все вызовы атрибута для функции, которая оформляется. Итак, теперь вы можете создать простой декоратор, который проверяет, что 2 аргумента указаны примерно так:
источник
@wraps
сказано в документации от ,@wraps
это просто удобная функция дляfunctools.update_wrapper()
. В случае декоратора класса, вы можете вызватьupdate_wrapper()
напрямую из вашего__init__()
метода. Таким образом, вам не нужно создаватьDecBase
на всех, вы можете просто включить на__init__()
изprocess_login
линии:update_wrapper(self, func)
. Это все.Начиная с Python 3.5+:
Псевдоним для
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)
как описано в документе . Это имеет неприятные последствия:Signature.bind()
.Теперь существует немного путаницы между
functools.wraps
декораторами, потому что очень часто для разработки декораторов используется оболочка функций. Но оба являются совершенно независимыми понятиями. Если вам интересно понять разницу, я реализовал вспомогательные библиотеки для обоих: decopatch, чтобы легко писать декораторы, и makefun, чтобы обеспечить замену для сохранения сигнатур@wraps
. Обратите внимание, чтоmakefun
опирается на тот же проверенный трюк, что и знаменитаяdecorator
библиотека.источник
это исходный код оберток:
источник
Обязательное условие: Вы должны знать, как использовать декораторы, особенно с обертками. Этот комментарий объясняет это немного ясно, или эта ссылка также объясняет это довольно хорошо.
Всякий раз, когда мы используем For, например: @wraps, за которым следует наша собственная функция-обертка. Согласно данным, приведенным в этой ссылке , это говорит о том, что
Таким образом, @wraps decorator на самом деле вызывает functools.partial (func [, * args] [, ** ключевые слова]).
Определение functools.partial () говорит, что
Что приводит меня к выводу, что @wraps вызывает функциюручный () и передает вашу функцию-обертку в качестве параметра. В конце концов функция PartAL () возвращает упрощенную версию, то есть объект того, что находится внутри функции-оболочки, а не саму функцию-оболочку.
источник
Короче говоря, functools.wraps - это обычная функция. Давайте рассмотрим этот официальный пример . С помощью исходного кода мы можем увидеть более подробную информацию о реализации и выполняемых шагах следующим образом:
Проверяя реализацию __call__ , мы видим, что после этого шага (левая сторона) обертка становится объектом, результатом которого является self.func (* self.args, * args, ** newkeywords) Проверяя создание O1 в __new__ , мы знаю, что self.func - это функция update_wrapper . Он использует параметр * args , правую обертку , в качестве первого параметра. Проверяя последний шаг update_wrapper , можно увидеть, что возвращается правая оболочка с некоторыми атрибутами, измененными по мере необходимости.
источник