Вопрос

В комментарии к этому ответ на другой вопрос, кто-то сказал, что они не уверены в том, что 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.Это объект класс Частичный
    2. Следующий шаг @О1... это обозначение декоратора в Python.Это значит

    оболочка = O1.__call__ (обертка)

    Проверяем выполнение __вызов__, мы видим, что после этого шага (левая часть)обертка становится объектом, возникшим в результате self.func(*self.args, *args, **newkeywords) Проверяем создание О1 в __новый__, мы знаем self.func это функция update_wrapper.Он использует параметр *аргументы, правая сторона обертка, в качестве его 1-го параметра.Проверка последнего шага update_wrapper, можно увидеть правую часть обертка возвращается с некоторыми атрибутами, измененными по мере необходимости.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top