初探python装饰器
初探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
。
理解
- 装饰器跟静态代理类似,可以为原有函数增加一些附属操作而不改变函数本身,但很大一点不同的是装饰器不会修改函数(类)的调用方式。
- 装饰器的执行是在模块加载的时候进行的,此时返回的是内部的闭包函数但不会真正执行,只有真正进行函数调用的时候才执行闭包函数
- 装载了装饰器后的函数(类)经过加载后,该引用指向的是闭包函数
python进阶 文章被收录于专栏
1