装饰器是python特有的一种语法,它允许程序在不修改原本函数或类的代码的情况下修改原函数或类的功能。除了python提供的几个内置装饰器(@classmethod、@staticmethod、@property),我们可以自己定义装饰器并使用。
装饰器其实就是函数和方法的调用,只要理解了其中的原理,就和写函数一样简单。
按照装饰器的类型以及被装饰的类型,装饰器主要分为以下几种,并且函数作为装饰器又细分为带参数与不带参数。
1. 函数作为函数的装饰器
函数作为装饰器时,是将被装饰的对象名传到装饰器函数的参数中,即可在装饰器内部调用被装饰的对象,当被装饰的对象是函数时,我们在装饰器内部可以直接调用该函数。
需要注意的是,装饰器的返回值必须是一个可以被调用的对象,因为我们在外部调用被装饰的函数时实际调用的是装饰器返回的函数,如果不返回一个可调用对象会报错。
不带参数
def timer(func):
from time import clock
def inner(*args, **kargs):
print(f'开始运行{
func.__name__}')
t1 = clock()
res = func(*args, **kargs)
t2 = clock()
print(f'运行结束,用时{
t2 - t1}s.')
return res
return inner
@timer
def myfunc1(n):
for i in range(n):
print(f'\r{
i + 1}', end='')
print()
def myfunc2(n):
for i in range(n):
print(f'\r{
i + 1}', end='')
print()
myfunc1(100)
print()
timer(myfunc2)(100)
上述代码运行结果如下
开始运行myfunc1
100
运行结束,用时0.00030649999999999997s.
开始运行myfunc2
100
运行结束,用时0.00024379999999999996s.
myfunc1被timer装饰,调用myfunc1时自动将myfunc1传入timer中,在timer中实现一些功能后,返回inner闭包函数。而外部调用myfunc1时其时调用的是inner闭包函数,传入的参数也是传入到了inner中。(myfunc2的调用结果与myfunc1一样,验证了上述观点。)
带参数
def calc(action):
def sum(func):
def inner(*args, **kargs):
func(*args)
print(f'a与b的和为{
args[0] + args[1]}.')
return args[0] + args[1]
return inner
def subtract(func):
def inner(*args, **kargs):
func(*args)
print(f'a与b的差为{
args[0] - args[1]}.')
return args[0] - args[1]
return inner
if action == '+':
return sum
elif action == '-':
return subtract
@calc('+')
def myfunc1(a, b):
print(f'a = {
a}, b = {
b}.')
@calc('-')
def myfunc2(a, b):
print(f'a = {
a}, b = {
b}.')
def myfunc3(a, b):
print(f'a = {
a}, b = {
b}.')
myfunc1(1, 2)
print()
myfunc2(4, 5)
print()
calc('+')(myfunc3)(5, 6)
上述代码运行结果如下
a = 1, b = 2.
a与b的和为3.
a = 4, b = 5.
a与b的差为-1.
a = 5, b = 6.
a与b的和为11.
带参数的装饰器其实也很简单,上述代码中真实的装饰器其实是sum和subtract,在外部套了一层calc以接受参数,当参数是‘+’时返回sum,是‘-’时返回subtract。@calc(‘+’)其实就是@sum,@calc(‘-’)其实就是@subtract,只不过外部无法调用sum和subtract,所以以带参数装饰器方式调用。(myfunc3的调用也验证了上述观点。)这样做的好处是可以将功能类似的装饰器统一起来,用不同的参数进行调用,一些框架中使用的就是这种方式。
2. 函数作为类的装饰器
函数作为类的装饰器时,是将类传入装饰器函数参数中,因此在内部闭包函数必须实现创建对象并将对象作为返回值这一过程
不带参数
def createObject(cls):
def inner(*args, **kwargs):
print(f'构造类{
cls.__name__}的对象.')
return cls(*args, **kwargs)
return inner
@createObject
class A:
def __init__(self, x):
self.x = x
def print(self):
print(self.x)
@createObject
class B:
def __init__(self, x):
self.x = x
def print(self):
print(self.x)
a = A(2)
a.print()
print()
b = B(3)
b.print()
上述代码运行结果如下
构造类A的对象.
2
构造类B的对象.
3
上述代码中,在创建对象时,将类传入了装饰器createObject中,在其中进行了创建对象并返回的过程,并在最后返回了闭包函数inner,因此可以将闭包函数inner看做类的__init__方法(只是看做,但不是)。
带参数
def createObject(x):
def innerCreateObject(cls):
def inner(*args, **kwargs):
print(f'构造类{
cls.__name__}的对象.')
return cls(x)
return inner
return innerCreateObject
@createObject('5')
class A:
def __init__(self, x):
self.x = x
def print(self):
print(self.x)
@createObject('6')
class B:
def __init__(self, x):
self.x = x
def print(self):
print(self.x)
a = A(2)
a.print()
print()
b = B(3)
b.print()
上述代码运行结果如下
构造类A的对象.
5
构造类B的对象.
6
与带参数的函数作为函数装饰器相同,带参数的函数作为类的装饰器也是一样的原理,上述代码中@createObject(参数)相当于@innerCreateObject。使用带参数的装饰器,改变了原本类的__init__方法的根本功能,创建对象时使用的参数其实是装饰器传入的参数。
3. 类作为函数的装饰器
类作为装饰器时,被装饰的对象被传入装饰器类的__init__方法中,调用被装饰对象时,调用的其实是装饰器类的__call__方法,因此必须在__init__方法中将传入的对象赋值给self实例属性,并重写__call__方法。
class CallFunc:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f'调用函数{
self.func.__name__}')
return self.func(*args, **kwargs)
@CallFunc
def myfunc1(x, y):
return x + y
def myfunc2(x, y):
return x + y
print(myfunc1(1, 2))
print()
print(CallFunc(myfunc2)(3, 4))
上述代码运行结果如下
调用函数myfunc1
3
调用函数myfunc2
7
上述代码中对myfunc2的调用过程解释了对myfunc1使用装饰器的过程,即将函数名传入__init__方法中,调用时调用的是__call__方法。
类作为装饰器没有带参数装饰器,因为这是逻辑原因。如果类作为装饰器带参数的话,首先是把参数传入了__init__方法中,那实际的装饰器就应该是__init__方法返回的装饰器,但是__init__方法没有返回值,因此类作为装饰器带参数在逻辑上是一个有矛盾的方式。
4. 类作为类的装饰器
类作为类的装饰器的一个较为重要的用法就是单例模式,即一个类只生成一个对象,再次生成时返回第一次创建的对象。
class SingleObject():
__obj = None
def __init__(self, cls):
self.__cls = cls
def __call__(self, *args, **kwargs):
if self.__obj is None:
print(f'构造{
self.__cls.__name__}对象.')
self.__obj = self.__cls(*args, **kwargs)
return self.__obj
@SingleObject
class MyClass():
def __init__(self, x, y):
self.x = x
self.y = y
def print(self):
print(f'x = {
self.x}, y = {
self.y}.')
mc1 = MyClass(1, 2)
mc1.print()
print()
mc2 = MyClass(3, 4)
mc2.print()
上述代码运行结果如下
构造MyClass对象.
x = 1, y = 2.
x = 1, y = 2.
可以看到,即使重新创建对象,对象的实例属性值也是第一次创建的对象的值。原理为将对象赋值给装饰器类的一个类属性,类属性只会在类创建时创建一份,当第二次创建MyClass对象时,检查装饰器类的类属性不为None,直接返回。
写在最后
装饰器知识Python的一个语法糖,是方便python开发而出现的,不要把它当做一个很难的东西,只要明白了其中的原理就能豁然开朗。