Python装饰器
什么是装饰器
装饰器是一种用来扩展原来的函数功能的一种函数,它的返回值也是一个函数,它可以在不改变原有函数代码的前提下给它增加新的功能。python装饰器在插入日志、性能测试、事物处理、缓存和权限校验等方面都有很好的应用。
为什么要使用装饰器
正如前面所说,我们可以在不改变函数代码的前提下增加新的功能,并且便于代码的复用。
例如,有如下两个函数:
def task_A():
print("do some work")
def task_B():
print("do some work")
当需要把这两个任务的运行信息打印出来时,你可能会这样做:
def task_A():
print("task A is running")
print("do some work")
def task_B():
print("task B is running")
print("do some work")
或者更有经验的人可能会把添加的打印功能封装成一个函数,然后在每个任务中调用这些函数。
以上的方法不仅需要每次调用函数,而且还会修改原有函数的代码,比较麻烦。而装饰器就可以很好的解决这些烦恼。
还是上面这个例子,我们使用装饰器简单的修改一下。
def log_info(func):
def wrapper(*args, **kwargs):
print(func.__name__+" is running")
return wrapper
@log_info
def task_A():
print("do some work")
@log_info
def task_B():
print("do some work")
task_A()
task_B()
这样,我们就可以在不改变原有函数的情况下增加了新的功能,提升了代码的复用性。Python中我们使用“@”语法糖来精简装饰器的代码。我们就不用task_A=log_info(task_A)这一句了。这句话就等同于@log_info。
结果如下:
task_A is running
task_B is running
带参数的装饰器
上述我们实现的是一个简单的装饰器,它只接受一个函数作为参数。但是有的时候我们想传入其他函数应该怎么办呢。幸好装饰器允许我们在调用时提供其他参数。
def log_info(code):
def decorator(func):
def wrapper(*args, **kwargs):
if code=="yes"
print(func.__name__+" is running")
return wrapper
return decorator
@log_info(code="yes")
def task_A():
print("do some work")
@log_info(code="yes")
def task_B():
print("do some work")
task_A()
task_B()
这里的log_info就是带有参数的装饰器,它实际上就是对原有的装饰器进行函数封装。
运行结果为:
task_A is running
task_B is running
类装饰器
类装饰器相对于函数装饰器具有灵活度大、高内聚及封装性等优点。实现类装饰器时我们可以使用__call__()方法,它是python中的内置方法。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并且返回一个函数,当我们使用语法糖"@"附加到某个函数上时,就会调用__call__()方法,以此实现装饰器函数的效果。
`
class log_info(object):
def __init__(self,func):
self.func=func
def __call__(self,*args,**kwargs):
print(self.func.__name__+" is running")
return self.func(*args,*kwargs)
@log_info
def task_A():
print("do some work")
task_A()
运行结果为:
task_A is running
do some work
带参数的类装饰器
与函数装饰器一样,类装饰器也可以传入参数。要实现带参数的类装饰器,那么构造器里接收的不是函数,而是要传入的参数,通过类把参数保存起来。然后在重载__call__()方法的时候传入一个函数并返回一个函数。我们直接看下面这个例子,看看与不带参数的类装饰器的区别。
class log_info(object):
def __init__(self,code='yes'):
self.code=code
def __call__(self,func): #接收参数
def wrapper(*args,**kwargs):
print(func.__name__+" is running")
func(*args,**kwargs)
return wrapper #返回函数
@log_info(code='yes')
def task_A():
print("do some work")
task_A()
运行结果为:
task_A is running
do some work
内置的装饰器
python中有三个内置的装饰器:@classmethod、@staticmethod和@property。内置装饰器与普通装饰器不同的地方在于它返回的不是函数,而是一个对象。
在讲内置装饰器之前,我们需要知道类的方法分为实例方法和静态方法,实例方法的调用需要先创建实例对象,而静态方法可以直接通过类名来调用。而下面讲的@staticmethod和@classmethod就有达到直接用类名调用的效果。
@staticmethod
将类中的方法装饰为静态方法,使得可以在不创建实例的情况下,直接使用类名引用方法,将类的方法与实例解绑。它与成员方法的区别是没有self参数,并且不能访问类的属性。
使用静态方法有利于代码的组织,把某些属于某个类的函数放在那个类中去,同时有利于命名空间的整洁。
class Test:
def __init__(self):
pass
@staticmethod
def hello():
print("hello!")
t=Test()
t.hello() #实例化对象访问
Test.hello() #类名访问
运行结果为:
hello!
hello!
上述代码相当于hello=staticmethod(hello),
@classmethod
将方法装饰为类方法。与静态方法类似,它不需要创建实例就可调用。
classmethod与成员方法的区别在于所接受的第一个参数不是self(类实例的指针),而是cls(当前类的具体类型)。
有了cls参数,就可以在该方法中调用类的属性、方法、实例化对象等。更重要的也是区别与在函数内部直接使用类名调用自身类的方式在于,有了cls参数,当进行类的继承时,cls会变成子类本身,避免了硬编码,而使用类名调用自身类会在继承时无法更新到子类。
class Test(object):
name='Jordan'
def reply(self):
print('how are you?')
@staticmethod
def hello():
print("hello!")
@classmethod
def greeting(cls):
print("hello!",cls.name) #调用类的属性
cls().reply()
cls.hello() #调用类的方法
Test.hello()
Test.greeting()
运行结果:
hello!
hello! Jordan
how are you?
hello!
@classmethod装饰器的使用相当于greeting=classmethod(greeting)。
@property
将类中的实例方法装饰为类属性。
class Man(object):
def __init__(self,name):
self.name=name
@property
def age(self):
return self._age
@age.setter
def age(self,value):
self._age=value
m=Man('Michael')
m.age='30'
print(m.age)
运行结果为:
30
这篇文章介绍了property的用法:Python的property函数
functools.wraps
装饰器的优点我们已经展示过了,它可以极大的复用代码。但是使用装饰器后原函数的原信息也会不见,比如__name__及参数列表等。还是代码说话。
def log_info(func):
def wrapper(*args, **kwargs):
print(func.__name__+" is running")
return func(*args, **kwargs)
return wrapper
@log_info
def task_A():
print("do some work")
task_A()
print(task_A.__name__)
print(task_A.__doc__)
运行结果为:
task_A is running
do some wok
wrapper
None
可以看到,task_A函数被wrapper取代了,它的__name__也变成了wrapper函数的信息了。
而functools.wraps就是来解决这个问题的,它能把原函数的元信息拷贝到装饰器函数中,使得装饰器函数也有与原函数一样的元信息。其中wraps本身也是一个装饰器。使用functools.wraps的代码如下:
from functools import wraps
def log_info(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__+" is running")
return func(*args, **kwargs)
return wrapper
@log_info
def task_A():
print("do some work")
task_A()
print(task_A.__name__)
print(task_A.__doc__)
运行结果如下:
task_A is running
do some wok
task_A
None
使用多个装饰器
除了上述的用法之外,我们还可以使用多个装饰器装饰一个函数,在这种情况下,函数的执行流程是什么样的呢,还是先给出一个例子。
def log_info(func):
def wrapper(*args, **kwargs):
print(func.__name__+" is running")
return func(*args, **kwargs)
return wrapper
def task_A(func):
def wrapper(*args, **kwargs):
print("do work A")
func(*args, **kwargs)
return wrapper
def task_B(func):
def wrapper(*args, **kwargs):
print("do work B")
func(*args, **kwargs)
return wrapper
@log_info
@task_A
@task_B
def task_C():
print("do work C")
task_C()
运行结果为:
wrapper is running
do work A
do work B
do work C
当多个装饰器装饰函数时,函数的调用顺序为自下而上,上述代码中使用三个装饰器相当于log_info(task_A(task_B(task_C))),即调用顺序为taskB,taskA,log_info。但是运行结果似乎表明函数的调用顺序恰恰相反。这是怎么回事呢?
下面这篇文章详细的解释了装饰器的执行顺序之谜,有兴趣的可以看看,在此我就不赘述。