Что делает 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
; вместо этого он будет указан как * 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'
Другие советы
Я очень часто использую классы, а не функции, для своих декораторов. У меня были некоторые проблемы с этим, потому что объект не будет иметь все те же атрибуты, которые ожидаются от функции. Например, объект не будет иметь атрибута __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)
Этот класс передает все вызовы атрибута для декорируемой функции. Итак, теперь вы можете создать простой декоратор, который проверяет, что 2 аргумента указаны примерно так:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
Начиная с Python 3.5+:
@functools.wraps(f)
def g():
pass
Является псевдонимом для g = functools.update_wrapper(g, f)
.Он делает ровно три вещи:
- он копирует
__module__
,__name__
,__qualname__
,__doc__
, и__annotations__
атрибутыf
наg
.Этот список по умолчанию находится вWRAPPER_ASSIGNMENTS
, вы можете увидеть это в источник функциональных инструментов. - он обновляет
__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
и декораторы, потому что очень частым вариантом использования декораторов является обертывание функций.Но оба являются совершенно независимыми понятиями.Если вам интересно понять разницу, я реализовал вспомогательные библиотеки для обоих: декопатч легко писать декораторы и подшучивать предоставить сохраняющую подпись замену для @wraps
.Обратите внимание, что makefun
опирается на тот же проверенный трюк, что и знаменитый decorator
библиотека.
это исходный код оберток:
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)
Условие: вы должны знать, как использовать декораторы, особенно с обертками. Этот комментарий объясняет это немного ясно или этот link также довольно хорошо это объясняют.
Каждый раз, когда мы используем For, например: @wraps, за которым следует наша собственная функция-обертка. Согласно сведениям, приведенным в этой ссылке , говорится, что
functools.wraps - это удобная функция для вызова update_wrapper () в качестве декоратора функции при определении функции-оболочки. Р>
Это эквивалентно частичному (update_wrapper, wrapped = wrapped, назначено = назначено, обновлено = обновлено).
Таким образом, @wraps decorator на самом деле вызывает functools.partial (func [, * args] [, ** ключевые слова]).
Определение functools.partial () говорит, что
Partical () используется для частичного применения функции, которая «замораживает» некоторую часть аргументов функции и / или ключевых слов, в результате чего создается новый объект с упрощенной подписью. Например, partal () может использоваться для создания вызываемого объекта, который ведет себя как функция int (), где базовый аргумент по умолчанию равен двум:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
Что подводит меня к выводу, что @wraps вызывает функциюручный () и передает в качестве параметра вашу функцию-обертку. В конце концов функция PartAL () возвращает упрощенную версию, т.е. объект того, что находится внутри функции-оболочки, а не саму функцию-оболочку.
Суммируя, functools.wraps это просто обычная функция.Давайте рассмотрим это официальный пример.С помощью исходный код, мы можем увидеть более подробную информацию о реализации и выполняемых шагах следующим образом:
- обертывания(ж) возвращает объект, скажем О1.Это объект класс Частичный
- Следующий шаг @О1... это обозначение декоратора в Python.Это значит
оболочка = O1.__call__ (обертка)
Проверяем выполнение __вызов__, мы видим, что после этого шага (левая часть)обертка становится объектом, возникшим в результате self.func(*self.args, *args, **newkeywords) Проверяем создание О1 в __новый__, мы знаем self.func это функция update_wrapper.Он использует параметр *аргументы, правая сторона обертка, в качестве его 1-го параметра.Проверка последнего шага update_wrapper, можно увидеть правую часть обертка возвращается с некоторыми атрибутами, измененными по мере необходимости.