python functools.wraps

这个装饰器是用来维护被装饰的方法中一些最基本的属性的 ,位于functools.py文件中,使用的格式如下:

from functools import wraps
def
outer(f): @wraps(f) def inner(*args,**kwargs): return f(*args,**kwargs) return inner

可以看到被装饰的方法被要求作为wraps的参数传入进去。

它的原型如下:

def wraps(wrapped,assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES):
  return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)

参数说明:

  wrapped:是即是被装饰并且要保证基本属性的方法;

  assigned:默认值是 WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__'),它是一个元组,确切的说它是一个可迭代对象;这个值指明了要明确保留下来的被装饰方法的属性值,这里的明确保留的意思是要用被装饰函数的属性值直接替换装饰器函数本身同名的属性的值,如__name__属性 __doc__属性这些因为是要明确保留的,所以必须直接用内部函数属性值替代装饰器函数的属性值,这也正是我们能继续看到内部函数这些属性的原因;

  updated:默认值是 WRAPPER_UPDATES = ('__dict__',),这同样是一个可迭代对象,是要用被装饰方法中同名的属性值更新装饰器同名的属性值,即字典的update方法,从相应的英文注释中可以推断出来;

从方法的实现代码看,它调用了一个叫做partial的可能是函数也可能是类的对象,传入的第一个参数是一个叫做update_wrapper的对象;我们先看一下update_wrapper,这其实是一个函数:

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:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
    # from the wrapped function when updating __dict__
    wrapper.__wrapped__ = wrapped # todo 这个属性暂时没有明白有什么地方会用到
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

 从源码的实现可以看到,根据assigned指定的属性,__doc__ __name__其实就是通过将内部被装饰函数的属性值直接付给wraps装饰器的对应属性的,而最后又将这个被更新后的装饰器函数返回。

再看partial,这个其实是一个类,是一个可执行类;只看跟wraps相关的部分代码:

 1 class partial:
 2     """New function with partial application of the given arguments
 3     and keywords.
 4     """
 5 
 6     __slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
 7 
 8     def __new__(*args, **keywords):
 9         if not args:
10             raise TypeError("descriptor '__new__' of partial needs an argument")
11         if len(args) < 2:
12             raise TypeError("type 'partial' takes at least one argument")
13         cls, func, *args = args
14         if not callable(func):
15             raise TypeError("the first argument must be callable")
16         args = tuple(args)
17 
18         if hasattr(func, "func"):
19             args = func.args + args
20             tmpkw = func.keywords.copy()
21             tmpkw.update(keywords)
22             keywords = tmpkw
23             del tmpkw
24             func = func.func
25 
26         self = super(partial, cls).__new__(cls)
27 
28         self.func = func
29         self.args = args
30         self.keywords = keywords
31         return self
32 
33     def __call__(*args, **keywords):
34         if not args:
35             raise TypeError("descriptor '__call__' of partial needs an argument")
36         self, *args = args
37         newkeywords = self.keywords.copy()
38         newkeywords.update(keywords)
39         return self.func(*self.args, *args, **newkeywords)

首先看__new__方法,它要求至少传递一个可被执行的对象,这个对象用来保留被装饰方法的相关属性,在wraps中即调用上面的update_wrapper函数,然后要保留的属性会被作为partial对象的实例属性保留在内部供实例被call时使用。

再看__call__,可以看到当它被执行时,本质上就是调用那个用来保留以及更新装饰器函数本身的方法,在这里装饰器是wraps,而那个方法即是update_wrapper。

 

最后来回顾一下整个wraps的过程:

1 from functools import wraps
2 def outer(f):
3     @wraps(f)
4     def inner(*args,**kwargs):
5         return f(*args,**kwargs)
6     return inner

首先第3行,wraps=wraps(f),它返回一个partial类的可执行实例对象,然后再执行第四行的inner=wraps(inner),此时在wraps中就会执行update_wrapper去用f的属性值更新wraps的属性值,这样必要的属性值就被保留下来了。

有两点还需要说明:

1 整个代码的逻辑还是比较清晰的,特别是解耦做的还行,如果想保留的属性是另一套内容,完全可以通过@wraps时自己指定;

2 上面的partial代码在wraps被调用时并没有执行,因为源码中在最后居然还有几行内容:

try:
    from _functools import partial
except ImportError:
    pass

partial的逻辑应该是通过c实现的,不过具体的逻辑就是这里的partial代码,如果注释掉上面这几行代码 ,完全不影响执行。

posted @ 2020-10-04 12:01  gold615  阅读(239)  评论(0编辑  收藏  举报