Python functools.wrapps做什么?

Python functools.wrapps做什么?,python,decorator,functools,Python,Decorator,Functools,在对此的评论中,有人说他们不确定functools.wrapps在做什么。所以,我问这个问题是为了在StackOverflow上有一个关于它的记录以供将来参考:functools.wrapps到底做了什么?当你使用装饰器时,你是在用一个函数替换另一个函数。换句话说,如果你有一个装饰师 def logged(func): def with_logging(*args, **kwargs): print(func.__name__ + " was called")

在对此的评论中,有人说他们不确定
functools.wrapps
在做什么。所以,我问这个问题是为了在StackOverflow上有一个关于它的记录以供将来参考:
functools.wrapps
到底做了什么?

当你使用装饰器时,你是在用一个函数替换另一个函数。换句话说,如果你有一个装饰师

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
将替换为带有日志记录的函数
。不幸的是,这意味着如果你说

print(f.__name__)
它将打印带有日志记录的
,因为这是新函数的名称。事实上,如果您查看
f
的docstring,它将是空的,因为带有日志记录的
没有docstring,因此您编写的docstring将不再存在。另外,如果您查看该函数的pydoc结果,它不会被列为只接受一个参数
x
;相反,它将被列为使用
*args
**kwargs
,因为这是使用_日志记录所需要的

如果使用decorator总是意味着丢失有关函数的信息,那么这将是一个严重的问题。这就是为什么我们有
functools.wrapps
。这将采用装饰器中使用的函数,并添加在函数名、docstring、参数列表等上进行复制的功能。由于
wrapps
本身就是装饰器,因此以下代码执行正确的操作:

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'

我经常使用类,而不是函数作为装饰器。我在这方面遇到了一些问题,因为一个对象不会具有函数所期望的所有相同属性。例如,对象将不具有属性
\uuuu name\uuuu
。我对此有一个特别的问题,很难追踪Django报告错误“对象没有属性”
\uuuuuu name\uuuuuuu
”的位置。不幸的是,对于类风格的装饰者来说,我不相信@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)
  • 先决条件:您必须知道如何使用装饰器,特别是包装。这解释得有点清楚,或者这也解释得很好

  • 无论何时我们使用例如:@wrapps,后跟我们自己的包装函数。根据本文给出的细节,它说

  • functools.wrapps是一个方便的函数,用于在定义包装函数时调用update_wrapper()作为函数装饰器

    它相当于部分(update_wrapper,wrapped=wrapped,assigned=assigned,updated=updated)

    所以@wraps decorator实际上调用了functools.partial(func[,*args][,**关键字])

    functtools.partial()的定义是

    partial()用于partial函数应用程序,它“冻结”函数参数和/或关键字的某些部分,从而生成具有简化签名的新对象。例如,partial()可用于创建一个可调用函数,其行为类似于int()函数,其中基参数默认为两个:


    这让我得出结论,@wrapps调用partial(),并将包装函数作为参数传递给它。最后的partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。

    简而言之,functools.wrapps只是一个常规函数。让我们考虑一下。在的帮助下,我们可以看到有关实现和运行步骤的更多详细信息,如下所示:

  • 包装(f)返回一个对象,例如O1。这是本书的一个主题
  • 下一步是@O1…,这是python中的装饰符号。这意味着
  • wrapper=O1.\uuuu调用(wrapper)


    检查的实现,我们看到在这一步之后,(左侧)包装器成为self.func(*self.args,*args,**newkeywords)检查\uu new\uuu>中O1的创建所导致的对象,我们知道self.func是函数update\u wrapper。它使用参数*args,即包装的右侧作为其第一个参数。检查update_wrapper的最后一步,可以看到右侧的wrapper被返回,并根据需要修改一些属性

    这是关于包装的源代码:

    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)
    
    从python 3.5+开始:

    @functools.wraps(f)
    def g():
        pass
    
    g=functools.update\u wrapper(g,f)
    的别名。它有三个功能:

    • 它复制了
      g
      f
      \uuuu模块
      \uuuu名称
      \uuuu文档
      \uu注释
      属性。此默认列表位于
      WRAPPER\u ASSIGNMENTS
      中,您可以在中看到它
    • 它使用
      f.\uuuu dict\uu
      中的所有元素更新
      \uuu dict\uuu
      。(请参阅源代码中的
      WRAPPER\u UPDATES
    • 它在
      g
    结果是
    g
    显示为与
    f
    具有相同的名称、docstring、模块名称和签名。唯一的问题是,关于签名,这实际上不是真的:只是默认情况下,
    inspect.signature
    遵循包装器链。您可以使用
    inspect.signature(g,follow\u wrapped=False)
    进行检查,如中所述。这会产生恼人的后果:

    • 即使提供的参数无效,包装器代码也将执行
    • 包装器代码无法使用其na轻松访问参数
      @functools.wraps(f)
      def g():
          pass