python @warps装饰器及源码剖析

python装饰器的原理是将装饰对象传入,将装饰对象加强后再返回,但是我们此时调用装饰对象的时候,其实是调用装饰器对象,如下:

@decorator
def fn():
    pass

@语法糖其实相当于decorator(fn)
python这种动态语言很多功能是以鸭子方式来实现的,即看着像鸭子,游着像鸭子我们就认为它是鸭子
这个也是一样,我们虽然调用的是装饰器,但是实现的效果一样,所以我们认为它就是一样的
但是这样会有一些坑,比如之前的装饰对象会有一些属性丢失,如下列例子:

def decorator_single_obj(cls, *args, **kwargs):
    instance = {}  # 创建字典来保存实例

    def get_instance(*args, **kwargs):
        if cls not in instance:  # 若实例不存在则新建
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]
    return get_instance


@decorator_single_obj
class Foo(object):
    age = 24

    def __init__(self, name):
        self.name = name

    @classmethod
    def test(cls):
        pass

我们用装饰器实现了单例模式,打印下面

obj1 = Foo("123")
print(Foo)
Foo.age

结果如下:

Traceback (most recent call last):
<function decorator_single_obj.<locals>.get_instance at 0x0000025AEDAF9158>
  File "E:/githubproject/Source-code/plus/decorator/decorator_single_object.py", line 28, in <module>
    Foo.age
AttributeError: 'function' object has no attribute 'age'

我们再访问Foo变成了函数对象,且再访问age属性没了,虽然我们通过实例对象obj1能正常访问,但是正常类属性和类方法还是应该由类来调用.
其他呢比如说原来对象的doc即文档说明等属性也丢失了,那么如何解决这个问题呢,这时就需要@warps装饰器了
@warps装饰器也是python的内置装饰器,它用来保存之前装饰对象的属性,保证属性不丢失,包括doc
这里需要在头部导入装饰器,并在函数类添加上

from functools import wraps


def decorator_single_obj(cls, *args, **kwargs):
    instance = {}  # 创建字典来保存实例

    @wraps(cls)   ##看这里!看这里
    def get_instance(*args, **kwargs):
        if cls not in instance:  # 若实例不存在则新建
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]
    return get_instance


@decorator_single_obj
class Foo(object):
    age = 24

    def __init__(self, name):
        self.name = name

    @classmethod
    def test(cls)

上面我们只是在get_instance上加上了@warps装饰器

这时我们再调用

obj1 = Foo("123")
print(Foo)
Foo.age
Foo.test

就不会出错了
这就是@warps的作用,保存装饰对象的属性
它的原理其实就是将原对象的属性取出来,再一一赋值给装饰器

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)

这一部分就是@warps的源码,难度也不是很大,

这里写图片描述
这里写图片描述
其中WRAPPER_ASSIGNMENTS为新添加的属性,不必一一深究,感兴趣的可以一一研究下,大部分都是python3的新特性

更正一下,qualname才会更新对象的名字

WRAPPER_UPDATES为原对象的dict属性并且会一一赋值给装饰器
这里返回的装饰器对象就包含原来对象的所有属性了
并且我们打印

print(Foo.__wrapped__)

结果:

<class '__main__.Foo'>

可以看到原来的对象保存在了装饰器的warpped属性中,且原对象的所有属性都也添加到了装饰器中
所以对象的属性在这里得到了保存

猜你喜欢

转载自blog.csdn.net/renyiforever/article/details/79965068