Talk is cheap, show me the code.所以,下面先给出一个最简单的例子,再来解释装饰器原理。
def log_it(func):
def wrapper(*args, **kwargs):
print("[Debug]: enter function {}()".format(func.__name__))
return func(*args, **kwargs) # if function func has return, remember to return
return wrapper
@log_it
def add_number(*args):
return sum(args)
print(add_number(1, 2, 3, 4))
print(add_number.__name__)
# 输出
[Debug]: enter function add_number()
10
wrapper
细心的你会发现,print(add_number.__name__)
输出的是wrapper,意味着装饰器的实际意思就是 add_number = log_it(add_number)
,所以执行add_number()
函数就相当于执行wrapper()
函数,而这也就是装饰器的原理啦。即通过传入一个函数将其包装成一个新的函数,赋予它额外的功能。
装饰器还有更大的灵活性,例如带参数的装饰器,比如上面的例子中,你想控制print
语句的内容是动态的,那么你可以给它传入参数,代码如下:
def log_it2(level='Debug'):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@log_it2(level='Info')
def add_number2(*args):
return sum(args)
print(add_number2(1, 2, 3, 4, 5))
# 输出
[Info]: enter function add_number2()
15
看到这里我估计有朋友要晕了,因为这个装饰器居然嵌套了三个函数定义,什么鬼?是不是瞬间感觉还不如学习C++,根本不存在函数嵌套,哈哈~~接下来来仔细分析一下:
add_number2.__name__
应该是inner_wrapper(不信你可以输出看一看);add_number2 = log_it2(debug='info')(add_number2)
, 从而说明add_number2.__name__
的值就是inner_wrapper.
类装饰器和函数装饰器一样,包括基本类装饰器和传参类装饰器两种类型,但是其形式略有不同,类装饰器必须实现魔法方法__call__
函数,关于__call__
函数以及更多魔法方法的用法请参考Python面向对象、魔法方法
示例代码如下:
class LogIt(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[DEBUG]: enter function {func}()".format(
func=self.func.__name__))
return self.func(*args, **kwargs)
@LogIt
def add_number(*args):
return sum(args)
print(add_number(1, 2, 3, 4))
我上面说形式上略有不同,可这一看,好像却是大有不同,这又是怎么实现的呢?首先LogIt类实现了构造方法__init__(self,func)
,它接收一个函数,这看起来很正常;但是,它还是实现了__call__
方法并返回了函数func
,这不正是我们开篇说的,接受一个函数并返回一个函数吗?这就是它的实现原理。我们可以验证一下:
>>>add_number
<__main__.LogIt object at 0x10e4dd7f0>
看上面的输出,add_number
变成一个函数了,所以我们不难想象,它实际过程是 add_number = LogIt(add_number),如此一来其自然而然就是LogIt的一个实例了。
那么类装饰器要怎么传递参数呢?请看下面的示例代码:
class LogIt2(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__))
return func(*args, **kwargs)
return wrapper
@LogIt2(level='INFO')
def add_number2(*args):
return sum(args)
print(add_number2(1, 2, 3, 4, 5))
如果你融会贯通了上面所蕴含的思想,应该不难推理出它的包装过程就是:add_number2 = LogIt('INFO')(add_number2)
,所以add_number2
此时应该是实际上是wrapper
函数。怎么知道我说的是对的呢?
>>> add_number2.__name__
wrapper
以上就是装饰器的一些基本使用了,多看几遍,多实践几次,你一定可以看懂!
以上我们只讨论了一个装饰器的使用,如果同时使用多个装饰器,会是怎样的执行流程呢?我们先直接上一个例子,看一看:
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
res = f(1)
print('res:', res)
# 输出结果
# Get in decorator_a
# Get in decorator_b
# Get in inner_b
# Get in inner_a
# Get in f
# res: 2
如果你是第一次看装饰器,且你的预想中也是这个输出,那么恭喜你,你一定是996ICU友情链接的天选之人;如果你看的目瞪口呆,其实也不要紧,只要你有995ICU友情链接2的精神,你也可以是王者。好吧,回到正题,其实关于多装饰器执行顺序的规则就是从里到外顺序执行,即最先调用最里层的装饰器,最后调用最外层的装饰器。其调用过程为:
f = decorator_b(decorator_a(f))
且最后f
的形式为:
# 注意下面不是可执行代码,缩进是为了表示逻辑关系
def inner_b(*args, **kwargs):
print('Get in inner_b')
# -----------inner_a-------------
print('Get in inner_a')
# -----------f-------------
print('Get in f')
return x * 2
我觉得,如果你看懂了上面这一段"伪代码",那么你就真的理解了装饰器原理。如果没看懂,不要紧,试着多看几遍就好;就好比心情不好,那就去吃一顿火锅,如果不够,那就再来一顿!
非常抱歉,本人才疏学浅,此处存在错误,经过严肃探究,发现property、classmethod、staticmethod不是装饰器,只是使用形式类似装饰器,实际上是描述器,文章其他部分应该没有问题。关于这三个描述器的理解,请见python描述器深度理解
在python中有以下几个常见的内置装饰器,它们都和python面向对象编程有关,下面分别做简要介绍:
这是python中抽象类"虚方法"的定义方式,一个类中如果存在@abc.abstractmethod装饰的方法,那么其不可以实例化对象,且继承的子类必须实现@abc.abstractmethod装饰的方法。
import abc
class A(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def load(self, _input):
pass
@abc.abstractmethod
def save(self, output, data):
pass
class B(A):
def load(self, _input):
return _input.read()
def save(self, output, data):
return output.write(data)
if __name__ == '__main__':
print(issubclass(B, A))
print(isinstance(B(), A))
print(A.__subclasses__())
# 输出
True
True
[<class '__main__.B'>]
类属性有三个装饰器: setter
, getter
, deleter
,它们都是在 property ()
的基础上做了一些封装,其中getter
装饰器和不带 getter
的属性装饰器效果是一样的。该特性最重要的功能之一就是能实现属性的参数检验。
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@score.deleter
def score(self):
del self._score
s = Student()
s.score = 10
print(s.score)
被@classmethod
装饰的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
class A(object):
# 属性默认为类属性(可以给直接被类本身调用)
num = "类属性"
# 实例化方法(必须实例化类之后才能被调用)
def func1(self): # self : 表示实例化类后的地址id
print("func1")
print(self)
# 类方法(不需要实例化类就可以被类本身调用)
@classmethod
def func2(cls): # cls : 表示没用被实例化的类本身
print("func2")
print(cls)
print(cls.num)
cls().func1()
# 不传递传递默认self参数的方法(该方法也是可以直接被类调用的,但是这样做不标准)
def func3():
print("func3")
print(A.num) # 属性是可以直接用类本身调用的
# A.func1() 这样调用是会报错:因为func1()调用时需要默认传递实例化类后的地址id参数,如果不实例化类是无法调用的
A.func2()
A.func3()
被@staticmethod
修饰的方法是静态方法,静态方法的参数可以根据业务需求传入,没有固定参数;然而前面的实例化方法第一个参数必须是self
,类方法第一个参数必须是cls
。静态方法,跟普通函数没什么区别,与类和实例都没有所谓的绑定关系,它只不过是碰巧存在类中的一个函数而已,不论是通过类还是实例都可以引用该方法。之所以将其放在类中,是因为该方法仅为这个类服务。
class Method(object):
def __init__(self, data):
pass
@staticmethod
def static_method():
print "This is static method in class Method"
为了更好的理解辨明实例方法、类方法、静态方法、抽象方法,我再举一个例子,从本质上来理一理它们之间的关系:
import abc
class ICU996():
@staticmethod
def static_m(self):
pass
@classmethod
def class_m(cls):
pass
def instance_m(self):
pass
@abc.abstractmethod
def abstract_m(self):
pass
>>>icu = ICU996()
>>>icu.static_m
<function ICU996.static_m at 0x11af930d0>
>>>icu.class_m
<bound method ICU996.class_m of <class 'test.ICU996'>>
>>>icu.instance_m
<bound method ICU996.instance_m of <test.ICU996 object at 0x11af54978>>
>>>icu.abstract_m
<bound method ICU996.abstract_m of <test.ICU996 object at 0x11af54978>>
从上面的输出结果不难看出,实例化方法和抽象方法本质上是一样的,都是绑定在实例上的方法;而类方法这是绑定在类上的方法;静态方法则实质上就是一个不同函数。
最后,我们来解决前面的出现的一个问题,在第一个实例中,我们发现被装饰的函数add_number.__name__
变成了wrapper,那么如果我们并不想让这样的事情发生,我们该怎么做呢?其实,这一点,可以用内置的装饰器wraps
来实现:
from functools import wraps
def log_it(func):
@wraps(func) # comment this line to see the diff
def wrapper(*args, **kwargs):
print("[Debug]: enter function {}()".format(func.__name__))
return func(*args, **kwargs)
return wrapper
@log_it
def add_number(*args):
"""
add numbers in tuple
:param args: should be numbers
:return: the sum of tuple
"""
return sum(args)
print(add_number.__name__, add_number.__doc__)
# 输出
add_number
add numbers in tuple
:param args: should be numbers
:return: the sum of tuple
它不仅可以使函数名保持不变,还可以保持函数原有的doc string。好,那么是不是应该思考一下它是怎样实现的呢?
from functools import partial
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned=WRAPPER_ASSIGNMENTS,
updated=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):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
这是python官方的实现方式,关于__module__
,__name__
等特殊属性,请参考python特殊属性、魔法方法;这里包装的过程简化来看就是add_number = update_wrapper(wrapper, add_number)
,其中update_wrapper
的功能是更新wrapper函数的一些特殊属性。
装饰器是python的一大难点,它本质上就是一个函数,它可以让其他函数在不需要变动的情况下增加额外的功能。装饰器常用于有切面的应用场景,如插入日志、性能测试等场景。多看、多试、多用,装饰器,其实也不难。