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