初探python装饰器
前言
本文用于记录总结自己在python装饰器学习过程中的理解,如有疑问欢迎讨论。
可变长参数
def wrapper(*args, **kwargs):
print('before test1 ...')
func(*args, **kwargs)
print('after test1 ...')
return wrapper
wrapper(a, b, c, k1 = 'v1',k2 = 'v2') 简单理解 :
- * args 在函数中视为元组中的元素;
- ** kwargs 视为字典中元素,其中k为字典中的key,v为字典中的value。
详见如下:
可变长参数
闭包
闭包指的是:延伸了作用域的函数。如内部函数对外部作用域的变量进行引用,则该内部函数即被认为是闭包。
- 闭包的作用在于保存了当前的运行状态。
- 装饰器中所返回的是闭包函数。
装饰器是什么
python装饰器可以看作是对原函数(类)的增强,比如函数运行前后添加内容,但不会改变原有函数内部的内容。
装饰器其实是对闭包的一种应用,结合可变长参数可以适配各种函数(类)。
装饰器的四种分类
函数装饰函数
以下例子来自python装饰器
不带参数装饰器
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs) # 此处拿到了被装饰的函数func
time.sleep(2) # 模拟耗时操作
long = time.time() - start
print('共耗时{}秒'.format(long))
return wrapper # 返回内层函数的引用
@timer
def add(a, b):
print(a + b)
if __name__ == '__main__':
add(1, 2) # 正常调用add
输出:
3
共耗时2.01200008392秒 装饰器的执行流程:
- 模块加载时,会执行
@后面的函数(上述例子中执行timer函数,其中的func代表add函数) - 执行timer函数后返回内部
wrapper函数。其中func为原add函数 - 调用add函数,实际是调用了
wrapper函数,先执行start = time.time(),再调用add函数,再执行之后的操作
可以理解为,当在func上增加装饰器后。在模块执行后,当前func指向了装饰器内返回的warpperr函数。当我们调用func时,其实是在调用warpper函数。
带参数装饰器
def auth(permission):
def _auth(func):
def wrapper(*args, **kwargs):
print("验证权限[{}]...".format(permission))
func(*args, **kwargs)
print("执行完毕...")
return wrapper
return _auth
@auth("add")
def add(a, b):
"""
求和运算
"""
print(a + b)
add(1, 2) # 正常调用add
输出:
验证权限[add]...
3
执行完毕... 与不带参数的装饰器类似,只是在执行装饰器的时候可以通过传参来按需配置某些属性
唯一注意的是该装饰器存在3层结构
执行流程:
- 模块加载时,会执行
@后面的函数(上述例子中执行auth函数,其中的permission代表auth函数的参数) - 执行auth函数后返回内部
_auth函数。其中func为原add函数 - 执行
_auth函数,返回内部的wrapper函数 - 调用add函数,实际是调用了
wrapper函数,先执行输出,再调用add函数,再执行之后的操作
多装饰器的执行顺序
def test1(func):
def wrapper(*args, **kwargs):
print('before test1 ...')
func(*args, **kwargs)
print('after test1 ...')
return wrapper #返回内层函数的引用
def test2(func):
def wrapper(*args, **kwargs):
print('before test2 ...')
func(*args, **kwargs)
print('after test2 ...')
return wrapper #返回内层函数的引用
@test2
@test1
def add(a, b):
print(a+b)
add(1, 2) #正常调用add
输出:
before test2 ...
before test1 ...
3
after test1 ...
after test2 ... 装饰器是可以嵌套调用的。当有多个装饰器作用于同一个函数的时候,会从最近的装饰器开始执行。比如上面例子中先执行@test1再执行@test2。
函数装饰类
例子来自:python装饰器4种类型
def wrapClass(cls):
def inner(a):
print('class name:', cls.__name__)
return cls(a)
return inner
@wrapClass
class Foo():
def __init__(self, a):
self.a = a
def fun(self):
print('self.a =', self.a)
m = Foo('xiemanR')
m.fun()
输出:
('class name:', 'Foo')
('self.a =', 'xiemanR') 执行顺序:
- 模块加载时,执行warpClass函数,其中cls为Foo该类,返回inner内部函数
- 执行Foo('xiemanR')时,实际调用inner函数,打印class name
- 调用Foo(a) 进行类初始化一个实例并返回给引用m
- m.fun调用Foo的fun函数
利用函数装饰类,可以对类初始化之前做一些额外的操作。上述例子只是在类进行初始化前打印类的名称。但在实际当中,亦可以通过反射向类中动态增加属性,或者将该类放入自定义容器进行管理
类装饰函数
class ShowFunName:
def __init__(self, level):
self.level = level
def __call__(self, func):
print('function name:', func.__name__)
def wrapper(*args, **kwargs):
print("[{level}]: the function {func}() is running...".format(level=self.level, func=func.__name__))
return func(*args, **kwargs)
return wrapper
@ShowFunName(level='info')
def Bar(a):
return a
if __name__ == '__main__':
print(Bar('xiemanR'))
结果:
('function name:', 'Bar')
[info]: the function Bar() is running...
xiemanR 执行流程:
- 模块加载时,初始化ShowFunName类实例,执行
init方法。其中level为入参 - 执行实例的
__call__函数,返回wrapper函数,将Bar指向wrapper - 调用
Bar函数,实际调用的是wrapper函数,wrapper函数内进行日志输出,并且执行原来的Bar函数并返回
注意:
以类作为装饰器,必须实现init跟call方法
__init__: 接收传入参数
__call__: 接受被装饰者(函数或类)
类装饰类
class ShowClassName(object):
def __init__(self, level):
self.level = level
def __call__(self, cls):
print('class name:', cls.__name__)
def wrapper(value):
print("[{level}]: the cls {cls}() is ready return ...".format(level=self.level, cls=cls.__name__))
return cls(value)
return wrapper
@ShowClassName(level='info')
class Foobar(object):
def __init__(self, a):
self.value = a
print 'init', self.value
def fun(self):
print('fun')
if __name__ == '__main__':
a = Foobar('xiemanR')
a.fun()
结果:
('class name:', 'Foobar')
[info]: the cls Foobar() is ready return ...
init xiemanR
fun 执行流程与类装饰函数类似,但需要注意的是wrapper方法的参数个数要跟Foobar中__init__中的一致,因为执行Foobar初始化实际调用的是wrapper方法,在wrapper内部才真正初始化Foobar。
理解
- 装饰器跟静态代理类似,可以为原有函数增加一些附属操作而不改变函数本身,但很大一点不同的是装饰器不会修改函数(类)的调用方式。
- 装饰器的执行是在模块加载的时候进行的,此时返回的是内部的闭包函数但不会真正执行,只有真正进行函数调用的时候才执行闭包函数
- 装载了装饰器后的函数(类)经过加载后,该引用指向的是闭包函数



京公网安备 11010502036488号