Что functools.обертывания делать?


в комментарии к ответ на другой вопрос, кто-то сказал, что они не были уверены, что functools.обертывания делали. Поэтому я задаю этот вопрос, чтобы была запись об этом на StackOverflow для дальнейшего использования: что делает functools.обертывания делать?

4 438

4 ответа:

когда вы используете декоратор, вы заменяете одну функцию на другую. Другими словами, если у вас есть декоратор

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 не имеет docstring, и поэтому docstring, который вы написали, больше не будет там. Кроме того, если вы посмотрите на результат pydoc для этой функции, он не будет указан как принимающую один аргумент x; вместо этого он будет указан как принимать *args и **kwargs потому что это то, что with_logging принимает.

если бы использование декоратора всегда означало потерю этой информации о функции, это было бы серьезной проблемой. Вот почему мы functools.wraps. Это берет функцию, используемую в декораторе, и добавляет функциональность копирования по имени функции, docstring, списку аргументов и т. д. И с тех пор 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)
  1. обязательное условие: Вы должны знать, как использовать декораторы и особенно с обертываниями. Это комментарий объясняет это немного ясно или это ссылке также объясняет это довольно хорошо.

  2. всякий раз, когда мы используем, например: @wraps, за которым следует наша собственная функция обертки. В соответствии с деталями, приведенными в этом ссылке говорится , что

functools.wraps-это удобная функция для вызова update_wrapper () как декоратор функции, при определении функции-оболочки.

Это эквивалентно частичной(update_wrapper, обернутый=укутав, присвоенный=назначенных обновленными=обновлено).

Итак, @ wraps decorator фактически дает вызов functools.частичный(func [, * args] [, * * keywords]).

В functools.частичное () определение говорит, что

частичный() используется для применения частичной функции, которая "замораживает" некоторую часть a Аргументы и/или ключевые слова функции, приводящие к новому объекту с упрощенной сигнатурой. Например, 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() и передает ему функцию-оболочку в качестве параметра. Partial() в конце возвращает упрощенную версию, т. е. объект того, что находится внутри оболочки функция, а не сама функция-оболочка.

короче, functools.обертывания - это просто обычная функция. Давайте рассмотрим это официальный пример. С помощью исходный код, мы можем увидеть более подробную информацию о реализации и беговых шагов следующим образом:

  1. wraps (f) возвращает объект, скажем O1. Это объект частичная класс
  2. следующий шаг: @O1... что обозначение декоратор в Python. Это значит

обертка=O1.__ call__(обертка)

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