详解Python修饰器
Python的修饰器(decorator)是一个非常强大的功能,一种优秀的设计模式,将重复的内容抽象出来,赋予一个函数其他的功能,但又不去改变函数自身。使得代码极其简洁,易于维护。但其本质上还是一个语法糖
现实问题
在实际的项目中,经常会遇到需要在原有项目上加一些功能,但是这些功能可能很快被删除掉,如何实现这种要求呢?下面举例一个简单的函数
def foo():
print('hello')
如果想打印该函数的执行时间:
from time import time
def foo():
t1 = time()
print('hello')
t2 = time()
print(t2 - t1)
这种在原有函数里面加代码,也不符合开闭原则,那么我可以新增加一个函数:
from time import time
def run_time(func):
def wrap():
t1 = time()
func()
t2 = time()
print(t2 - t1)
return wrap
这么写的确不用修改原来的代码,但是新增函数还是要被调用的。Python参照Java,提供了注解来优化该场景,上述场景可以简写如下
from time import time
def run_time(func):
def wrap():
t1 = time()
func()
t2 = time()
print(t2 - t1)
return wrap
@run_time
def foo():
print('hello')
修饰函数
Python注解修饰函数时,函数定义的不同,注解的实现也不同
无参数无返回
上面的例子就是无参数无返回值
无参数有返回
from time import time
def run_time(func):
def wrap():
t1 = time()
r = func()
t2 = time()
print(t2-t1)
return r
return wrap
@run_time
def foo():
return 'hello'
print(foo())
有参数无返回
有参数无返回这种情况较少
from time import time
def run_time(func):
def wrap(a, b):
t1 = time()
func(a, b)
t2 = time()
print(t2 - t1)
return wrap
@run_time
def foo(a, b):
print(a + b)
foo(2, 45)
有参数有返回
from time import time
def run_time(func):
def wrap(a, b):
t1 = time()
r=func(a, b)
t2 = time()
print(t2 - t1)
return r
return wrap
@run_time
def foo(a, b):
return a + b
print(foo(2, 45))
装饰器带参数
有时候装饰器可能需要一些参数,这个场景更复杂,具体实现也就是多层套娃来容纳不同的参数
def namedDecorator(name):
def run_time(func):
def wrap(a, b):
print('this is:{}'.format(name))
r = func(a, b)
return r
return wrap
return run_time
@namedDecorator("装饰器带参数")
def foo(a, b):
return a + b
print(foo(2, 45))
消除副作用
上述的装饰后,foo函数已经不是原来的foo了,已经是被装饰后的函数了
def namedDecorator(name):
def run_time(func):
def wrap(a, b):
'''my decorator'''
print('this is:{}'.format(name))
r = func(a, b)
return r
return wrap
return run_time
@namedDecorator("装饰器带参数")
def foo(a, b):
"""example docstring"""
return a + b
print(foo(2, 45))
print(foo.__name__, foo.__doc__)
返回
this is:装饰器带参数
47
wrap my decorator
使用@wraps装饰器可以消除这种影响
from functools import wraps
def namedDecorator(name):
def run_time(func):
@wraps(func)
def wraper(a, b):
'''my decorator'''
print('this is:{}'.format(name))
r = func(a, b)
return r
return wraper
return run_time
@namedDecorator("装饰器带参数")
def foo(a, b):
"""example docstring"""
return a + b
print(foo(2, 45))
print(foo.__name__, foo.__doc__)
执行结果
this is:装饰器带参数
47
foo example docstring
多个装饰器
如果想要多个装饰器装饰同一个函数会怎样呢?
执行顺序
from time import time
def run_time_1(func):
def wrap(a, b):
r = func(a, b)
print('run_time_1')
return r
return wrap
def run_time_2(func):
def wrap(a, b):
r = func(a, b)
print('run_time_2')
return r
return wrap
@run_time_1
@run_time_2
def foo(a, b):
return a + b
print(foo(2, 45))
返回结果:
run_time_2
run_time_1
47
结论:离被修饰函数最近的一侧先执行
包裹形式
我们在上面的代码上加几行代码
from time import time
def run_time_1(func):
def wrap(a, b):
"""run_time_1 docstring"""
print(func.__name__, func.__doc__)
r = func(a, b)
print('run_time_1')
return r
return wrap
def run_time_2(func):
def wrap(a, b):
"""run_time_2 docstring"""
r = func(a, b)
print('run_time_2')
return r
return wrap
@run_time_1
@run_time_2
def foo(a, b):
"""foo docstring"""
return a + b
print(foo(2, 45))
print(foo.__name__, foo.__doc__)
输出结果:
wrap run_time_2 docstring
run_time_2
run_time_1
47
wrap run_time_1 docstring
结论:这是洋葱一样,一层包一层的结构
当然这可以使用@wrap消除影响,就不加代码了
修饰类
装饰器还可以装饰类,本质不变,都是执行一段自定义代码后再返回,可以用套娃,洋葱打比方
def decorator(cls):
print("这里可以写被装饰类新增的功能")
return cls
@decorator
class A(object):
def __init__(self):
pass
def test(self):
print("test")
执行结果
这里可以写被装饰类新增的功能
与装饰函数不同的是,被装饰的函数需要调用下,被装饰的类,不需要调用,申明阶段即可发挥作用