Python 中的函数装饰器简介
1.装饰器(decorator)简介
装饰器是 Python 中一个非常有用和强大的工具,它的作用对象是函数,实现的功能是在我们调用函数之前和之后,再为函数添加一些额外的功能。通过函数装饰器可以有效地提高代码的灵活性,复用能力,以及简化代码。
2.装饰器的原理
首先我们来简单定义一个“Hello World!"函数,如下:
1 | def say_hello_world(): |
该函数运行之后会打印"Hello World!",
Hello World!
打完招呼之后,我们又产生了新的需求,即是需要说再见,最简单的想法即是直接修改原函数,添加一行说再见,但这种方法在一切皆对象的 Python 世界中一点也不优雅,一个更好的方法是:在不修改原函数的情况下,我们对原函数做一个“包装”,为原函数添加新的功能,添加的代码为:
1 | def add_bye_world(func): |
运行结果为:
Hello World!
Bye World!
可以看到,这里我们定义的 add_bye_world 函数是将函数作为参数,在内部在嵌套定义了一个新的函数 wrapper,该内部函数在调用了传入函数之后,同时还添加一行说再见的代码,这样就实现了我们需求,然后在外部调用时我们返回这个内部函数,这样就实现了在不修改原函数的前提下为函数添加一些新的功能。
在上述过程中,我们实际上就相当于定义了一个装饰器,关于 Python 中装饰器的标准用法,Python 官网给出了说明:返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。以下为使用装饰器改写的以上代码:
1 | def add_bye_world(func): |
运行结果为:
Hello World!
Bye World!
可以看到,要使用装饰器,我们只需要在对应的函数前面加上 @add_bye_world 就可以,其中 add_bye_world 就是我们自定义的装饰器,这一过程相当于执行了命令:
1 | say_hello_world = add_bye_world(say_hello_world) |
关于函数装饰器更多的应用可以参考后面列出的参考资料。
参考资料:
3.一个值得注意的地方
我们在对函数使用了装饰器之后,会导致函数的 __name__、__doc__等属性发生改变,如下:
1 | def add_bye_world(func): |
运行结果为:
wrapper
可以看到原本名为 say_hello_world 的函数变为了 wrapper,如果我们要保留原函数的名称等属性,需要通过使用 Python 自带模块 functools 中的 wraps 来保留函数的元信息,相应的在装饰器定义处加入一行代码就可以:
1 | from functools import wraps |
运行结果为:
say_hello_world
4.Python 中一些常见的装饰器
classmethod、staticmethod
classmethod 和 staticmethod 都是在类中定义方法时使用,它们的作用都是将类中的实例化方法转化为类方法,其中实例化方法的意思是需要将类实例化之后才能调用,而类方法的意思是在调用的时候,我们并不需要将类实例化,而是可以直接通过类来调用。二者同时也存在一定的差别,其中 classmethod 会接收该类作为第一个隐参,通常为 cls,而 staticmethod 不会。如下:
1 | class SayHello: |
运行结果为:
Hello Cat!
Hello World!
Hello Dog!
其中 say_hello_2、say_hello_3 为类方法,可以直接通过类来调用,而 say_hello_1 为实例方法,需要将类实例化后调用,直接通过类调用会报错。
参考资料:
property
property 装饰器同样通常在类方法定义中使用,其作用是创建一个与方法同名的只读属性,常用作读取类定义中的私有变量,如下:
1 | class Animals: |
运行结果为:
Dog
可以看到这里由于我们使用了 @property 装饰器,导致创建了一个 animal_name 的属性,这个属性返回的是类中私有变量 __name 的值,我们可以在类实例化之后直接调用该属性,这里需要注意的是:调用的时候不要加括号,因为这里我们已经将相应的方法转化为属性,而不是方法。另外,如果我们直接访问实例的私有变量 __name,程序将会抛出错误 AttributeError 。
参考资料:
abstractmethod
同样通常在方法定义中使用,其作用是指定抽象基类中的抽象方法,抽象基类主要有两个特点:
- 抽象基类不能被实例化,只能被继承
- 子类必须实现抽象基类里定义的抽象方法,否则不能被实例化
1 | from abc import ABCMeta, abstractclassmethod |
运行结果为:
Animal: Dog
上面代码中,Animals 为抽象基类,其中 print_animal_name 这一方法为抽象方法,因此在之后所有的继承该类的类中,都需要在相应的类中实现该方法,否则类将不能实例化,这里 Dog 类就是继承自该抽象基类。
有关抽象基类和抽象方法的详细解释可以参考以下几篇文章: