对functools.wraps函数的理解

函数定义:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
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:
        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
    # 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)

应用示例:

import functools

def outer(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        print(f"In inner() name={inner.__name__}")
        func(*args, **kwargs)
    return inner

@outer
def add(a, b):
    print(a + b)

add(1, 2)

程序输出:

In inner() name=add       #inner.__name__属性被修改为add了
3

函数注解的翻译或理解:

1,update_wrapper函数
修改一个包装函数使它看起来像被包装函数
wrapper是被修改函数
wrapped是源函数
assigned是一个元组,命名从wrapped函数直接赋值给wrapper函数的属性(默认是functools.WRAPPER_ASSIGNMENTS)
updated是一个元组,命名wrapper的属性,这些属性被对应的wrapped函数的属性修改(默认是functools.WRAPPER_UPDATES)
2,wraps函数
装饰器工厂用来应用update_wrapper()到一个wrapper函数(wrapper函数是update_wrapper()的参数wrapper指代的函数,在示例中是inner函数)
返回一个装饰器(一个可调用partial实例对象),该装饰器调用update_wrapper(),update_wrapper()以被装饰的函数(示例中的inner函数)作为wrapper实参,并以wraps()的参数(wrapped,assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES)作为剩余的实参。wraps()的默认参数(assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES)和update_wrapper()的默认参数是一样的。
这是一个便利函数用以简化应用partial()于update_wrapper()(update_wrapper函数是实际起作用的函数,wraps和partial只是便利设施)

关于调用参数:
在wraps函数里返回语句是这样的:
return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)
在partial类里__call__()方法是这样定义的:
def __call__(self, /, *args, **keywords):
    keywords = {**self.keywords, **keywords}
    return self.func(*self.args, *args, **keywords)
我们注意到生成partial对象时三个参数是用keyword形式传递的,没有位置参数传给partial对象,所以partial对象的args属性为空。当partial对象作为装饰器装饰后跟的函数时,被装饰的函数作为位置参数传给partial对象的__call__()方法,该方法调用update_wrapper函数,因为args属性为空,并且位置参数先于关键字参数匹配,所以该函数传给了update_wrapper函数的wrapper参数。

最后谈谈装饰器涉及到的三个函数的关系,以应用示例里的函数来举例:
outer(func)是装饰器函数,add(a, b)是被装饰函数。对于add函数和inner函数来说,inner函数是包装函数,因为它把add函数包装进了自己的实现,add函数是被包装函数。
经过@functools.wraps(func)装饰后,inner函数的属性更改了,但是函数没有被替换,因为update_wrapper()最后return wrapper。
inner函数扮演了两个角色,一方面它是一个被wraps()返回的partial对象装饰的函数,同时它也是一个包装add()的函数。

你可能感兴趣的:(python,装饰器)