快捷搜索:  汽车  科技

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器有了以上基础知识,我们就可以开始探索类装饰器了。类似于函数装饰器,我们也由简到难一步步探究类装饰器,它们的思想与函数装饰器非常相似,只有些微小的差别。上面的例子中,result = add(3) 就使用了刚刚实例化的对象add,数值3被传入函数__call__中参与计算,返回计算的结果4,此处add完全表现得像一个函数。熟悉Python面向对象编程的读者应该非常熟悉该方法,在对象创建后,该方法会被调用,用来初始化对象自有的一些属性值。在上面的例子中,add对象创建时,数值1被传入方法__init__,作为一个属性存储在对象add中。当我们给一个类实现该魔法方法时,由该类生成的对象即变成一个可调用对象-可以以函数调用的形式使用该对象。

简介

前文(Python实验室-函数装饰器)我们已经研究了Python函数装饰器的运行机制,函数装饰器以函数实现,修饰另一个函数,隐式替换被修饰函数的实现。在万物皆对象的Python世界中,函数其实也是一种对象,对象也可以表现出函数的行为-可调用对象。类装饰器就是以可调用对象实现的装饰器。

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(1)

准备

在着手研究类装饰器之前,我们需要先学习Python对象的两个魔法方法:"__init__" 和 "__call__"。

我们从一个简单的例子开始:

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(2)

运行结果:4

  • __init__:

熟悉Python面向对象编程的读者应该非常熟悉该方法,在对象创建后,该方法会被调用,用来初始化对象自有的一些属性值。

在上面的例子中,add对象创建时,数值1被传入方法__init__,作为一个属性存储在对象add中。

  • __call__:

当我们给一个类实现该魔法方法时,由该类生成的对象即变成一个可调用对象-可以以函数调用的形式使用该对象。

上面的例子中,result = add(3) 就使用了刚刚实例化的对象add,数值3被传入函数__call__中参与计算,返回计算的结果4,此处add完全表现得像一个函数。

有了以上基础知识,我们就可以开始探索类装饰器了。类似于函数装饰器,我们也由简到难一步步探究类装饰器,它们的思想与函数装饰器非常相似,只有些微小的差别。

探索

无参类装饰器修饰无参函数

无参类装饰器有些特别,我们会多花些时间在这上面:

  • 方案一

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(3)

运行结果:

Pre func call.

Func is called.

Post func call.

运行结果显示功能已实现,提供方案一主要是和函数装饰器进行对比,方便我们理解,实际应用中我们很少这样实现,因为需要一个显示实例化的过程。

从代码中,我们可以看出主要需要四步:

  1. 定义装饰器的类
  2. 实例化装饰器类的对象
  3. 利用装饰器对象修饰函数
  4. 调用被修饰的函数

回忆函数装饰器,我们会先定义装饰器函数,函数名实际上便引用该函数的一个实例,然后我们使用'@'符号加上该函数实例,用来修饰其他的函数,注意,这里是用函数实例来修饰其他函数。对比类装饰器,在定义了装饰器的类后,我们也需要先得到一个装饰器的实例,可以通过显示实例化实现。

和函数装饰器一样,Python解析器在解析'@'符号时会自动增加一行代码:

func = decorate(func)

在执行decorate(func)时,对象decorate的函数__call__会被调用,返回内部函数wrapper。

func() => decorate(func)() => wrapper()

在调用func()时,等效于调用wrapper(),得到最终结果。

  • 方案二:

方案一需要显示实例化类装饰器,其实我们可以将实例化过程放到符号'@'后面,注意,此时Decorate后面的扩号不能省略,否则会编译出错。

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(4)

运行结果:

Pre func call.

Func is called.

Post func call.

装饰过程:func = Decorate()(func)

调用过程:func() => Decorate()(func)() => wrapper()

  • 方案三:

方案二对于无参装饰器需要显示保留一个括号,略显繁琐,能不能实现像函数装饰器一样的效果呢?可以,请看方案三:

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(5)

运行结果:

Pre func call.

Func is called.

Post func call.

方案三在使用上与函数装饰器无差别,__call__函数内也不需要一个wrapper函数,但是func需要作为__init__的一个属性,并且保存于装饰器实例中。

装饰过程:func = Decorate(func)

调用过程:func() = Decorate(func)()

无参类装饰器修饰带参函数

  • 方案一:

这里的调整主要集中在__call__函数内的wrapper函数的参数。

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(6)

运行结果:

Pre func call.

Func is called. hello

Post func call.

装饰过程:func = decorate(func)

调用过程: func('hello') => decorate(func)('hello') => wrapper('hello')

  • 方案二:

这里的调整主要集中在__call__函数内的wrapper函数的参数。

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(7)

运行的结果:

Pre func call.

Func is called. hello

Post func call.

装饰过程:func = Decorate()(func)

调用过程: func('hello') => Decorate()(func)('hello') => wrapper('hello')

  • 方案三:

这里的调整主要集中在__call__函数的参数。

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(8)

运行的结果:

Pre func call.

Func is called. hello

Post func call.

装饰过程:func = Decorate(func)

调用过程: func('hello') => Decorate(func)('hello')

带参类装饰器修饰无参函数

因为带参装饰器始终需要一个括号,我们只需要将无参装饰器第二种方案扩充一下,调整__init__函数的参数,就可以得到:

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(9)

运行的结果:

Pre func call. Decorator

Func is called.

Post func call. Decorator

装饰过程:func = Decorate(dec_param)(func)

调用过程: func() => Decorate("Decorator")(func)() => wrapper()

带参类装饰器修饰带参函数

python装饰器的写法以及应用场景:Python实验室我是类但我也可以做装饰器(10)

运行的结果:

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参数,代表对象实例本身。这两种方法在应用装饰器时与普通函数并无显著区别,此处不再赘述。

猜您喜欢: