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屬性中,且原對象的所有屬性都也添加到了裝飾器中
所以對象的屬性在這裏得到了保存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章