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属性中,且原对象的所有属性都也添加到了装饰器中
所以对象的属性在这里得到了保存