python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器
python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器有了以上基础知识,我们就可以开始探索类装饰器了。类似于函数装饰器,我们也由简到难一步步探究类装饰器,它们的思想与函数装饰器非常相似,只有些微小的差别。上面的例子中,result = add(3) 就使用了刚刚实例化的对象add,数值3被传入函数__call__中参与计算,返回计算的结果4,此处add完全表现得像一个函数。熟悉Python面向对象编程的读者应该非常熟悉该方法,在对象创建后,该方法会被调用,用来初始化对象自有的一些属性值。在上面的例子中,add对象创建时,数值1被传入方法__init__,作为一个属性存储在对象add中。当我们给一个类实现该魔法方法时,由该类生成的对象即变成一个可调用对象-可以以函数调用的形式使用该对象。
简介前文(Python实验室-函数装饰器)我们已经研究了Python函数装饰器的运行机制,函数装饰器以函数实现,修饰另一个函数,隐式替换被修饰函数的实现。在万物皆对象的Python世界中,函数其实也是一种对象,对象也可以表现出函数的行为-可调用对象。类装饰器就是以可调用对象实现的装饰器。
在着手研究类装饰器之前,我们需要先学习Python对象的两个魔法方法:"__init__" 和 "__call__"。
我们从一个简单的例子开始:
运行结果:4
- __init__:
熟悉Python面向对象编程的读者应该非常熟悉该方法,在对象创建后,该方法会被调用,用来初始化对象自有的一些属性值。
在上面的例子中,add对象创建时,数值1被传入方法__init__,作为一个属性存储在对象add中。
- __call__:
当我们给一个类实现该魔法方法时,由该类生成的对象即变成一个可调用对象-可以以函数调用的形式使用该对象。
上面的例子中,result = add(3) 就使用了刚刚实例化的对象add,数值3被传入函数__call__中参与计算,返回计算的结果4,此处add完全表现得像一个函数。
有了以上基础知识,我们就可以开始探索类装饰器了。类似于函数装饰器,我们也由简到难一步步探究类装饰器,它们的思想与函数装饰器非常相似,只有些微小的差别。
探索无参类装饰器修饰无参函数
无参类装饰器有些特别,我们会多花些时间在这上面:
- 方案一
运行结果:
Pre func call.
Func is called.
Post func call.
运行结果显示功能已实现,提供方案一主要是和函数装饰器进行对比,方便我们理解,实际应用中我们很少这样实现,因为需要一个显示实例化的过程。
从代码中,我们可以看出主要需要四步:
- 定义装饰器的类
- 实例化装饰器类的对象
- 利用装饰器对象修饰函数
- 调用被修饰的函数
回忆函数装饰器,我们会先定义装饰器函数,函数名实际上便引用该函数的一个实例,然后我们使用'@'符号加上该函数实例,用来修饰其他的函数,注意,这里是用函数实例来修饰其他函数。对比类装饰器,在定义了装饰器的类后,我们也需要先得到一个装饰器的实例,可以通过显示实例化实现。
和函数装饰器一样,Python解析器在解析'@'符号时会自动增加一行代码:
func = decorate(func)
在执行decorate(func)时,对象decorate的函数__call__会被调用,返回内部函数wrapper。
func() => decorate(func)() => wrapper()
在调用func()时,等效于调用wrapper(),得到最终结果。
- 方案二:
方案一需要显示实例化类装饰器,其实我们可以将实例化过程放到符号'@'后面,注意,此时Decorate后面的扩号不能省略,否则会编译出错。
运行结果:
Pre func call.
Func is called.
Post func call.
装饰过程:func = Decorate()(func)
调用过程:func() => Decorate()(func)() => wrapper()
- 方案三:
方案二对于无参装饰器需要显示保留一个括号,略显繁琐,能不能实现像函数装饰器一样的效果呢?可以,请看方案三:
运行结果:
Pre func call.
Func is called.
Post func call.
方案三在使用上与函数装饰器无差别,__call__函数内也不需要一个wrapper函数,但是func需要作为__init__的一个属性,并且保存于装饰器实例中。
装饰过程:func = Decorate(func)
调用过程:func() = Decorate(func)()
无参类装饰器修饰带参函数
- 方案一:
这里的调整主要集中在__call__函数内的wrapper函数的参数。
运行结果:
Pre func call.
Func is called. hello
Post func call.
装饰过程:func = decorate(func)
调用过程: func('hello') => decorate(func)('hello') => wrapper('hello')
- 方案二:
这里的调整主要集中在__call__函数内的wrapper函数的参数。
运行的结果:
Pre func call.
Func is called. hello
Post func call.
装饰过程:func = Decorate()(func)
调用过程: func('hello') => Decorate()(func)('hello') => wrapper('hello')
- 方案三:
这里的调整主要集中在__call__函数的参数。
运行的结果:
Pre func call.
Func is called. hello
Post func call.
装饰过程:func = Decorate(func)
调用过程: func('hello') => Decorate(func)('hello')
带参类装饰器修饰无参函数
因为带参装饰器始终需要一个括号,我们只需要将无参装饰器第二种方案扩充一下,调整__init__函数的参数,就可以得到:
运行的结果:
Pre func call. Decorator
Func is called.
Post func call. Decorator
装饰过程:func = Decorate(dec_param)(func)
调用过程: func() => Decorate("Decorator")(func)() => wrapper()
带参类装饰器修饰带参函数
运行的结果:
Pre func call. Decorator
Func is called. hello
Post func call. Decorator
装饰过程:func = Decorate(dec_param)(func)
调用过程: func('hello') => Decorate("Decorator")(func)('hello') => wrapper('hello')
总结Python中类装饰器本质上与函数装饰器并无显著差别,类装饰器的参数需要我们显示地保存为实例的属性,而函数装饰器参数被隐式保存于函数闭包之中。
学习探索Python装饰器的实现机制,不仅让我们对装饰器的使用得心应手,而且在某些应用场景下能触发一些新点子。如能理解上述装饰器的实现机制,相信大家对函数、面向对象的理解也会上升一个层次。
到目前为止,我们的装饰器都是应用在函数上,其实对象的创建也是一个函数调用的过程,因此,装饰器也可以应用于类上,后文我们再进一步分析。
P.S.
Python中,装饰器应用于函数,此函数也包括类的静态方法与动态方法,静态方法相比普通函数,多了第一个cls参数,代表类本身;动态方法相比普通函数也多了第一个self参数,代表对象实例本身。这两种方法在应用装饰器时与普通函数并无显著区别,此处不再赘述。