Python 中的函数装饰器简介

1.装饰器(decorator)简介

装饰器是 Python 中一个非常有用和强大的工具,它的作用对象是函数,实现的功能是在我们调用函数之前和之后再为函数添加一些额外的功能。通过函数装饰器可以有效地提高代码的灵活性,复用能力,以及简化代码。

2.装饰器的原理

首先我们来简单定义一个“Hello World!"函数,如下:

1
2
def say_hello_world():
print("Hello World!")

该函数运行之后会打印"Hello World!",

Hello World!

打完招呼之后,我们又产生了新的需求,即是需要说再见,最简单的想法即是直接修改原函数,添加一行说再见,但这种方法在一切皆对象的 Python 世界中一点也不优雅,一个更好的方法是:在不修改原函数的情况下,我们对原函数做一个“包装”,为原函数添加新的功能,添加的代码为:

1
2
3
4
5
6
7
def add_bye_world(func):
def wrapper():
func()
print("Bye World!")
return wrapper
say_hello_bye_world = add_bye_world(say_hello_world)
say_hello_bye_world()

运行结果为:

Hello World!

Bye World!

可以看到,这里我们定义的 add_bye_world 函数是将函数作为参数,在内部在嵌套定义了一个新的函数 wrapper,该内部函数在调用了传入函数之后,同时还添加一行说再见的代码,这样就实现了我们需求,然后在外部调用时我们返回这个内部函数,这样就实现了在不修改原函数的前提下为函数添加一些新的功能。

在上述过程中,我们实际上就相当于定义了一个装饰器,关于 Python 中装饰器的标准用法,Python 官网给出了说明:返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。以下为使用装饰器改写的以上代码:

1
2
3
4
5
6
7
8
9
10
11
def add_bye_world(func):
def wrapper():
func()
print("Bye World!")
return wrapper

@add_bye_world
def say_hello_world():
print("Hello World!")

say_hello_world()

运行结果为:

Hello World!

Bye World!

可以看到,要使用装饰器,我们只需要在对应的函数前面加上 @add_bye_world 就可以,其中 add_bye_world 就是我们自定义的装饰器,这一过程相当于执行了命令:

1
say_hello_world = add_bye_world(say_hello_world)

关于函数装饰器更多的应用可以参考后面列出的参考资料。

参考资料:

  1. https://docs.python.org/zh-cn/3/glossary.html#term-decorator
  2. https://www.zhihu.com/question/325817179/answer/798679602
  3. https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584

3.一个值得注意的地方

我们在对函数使用了装饰器之后,会导致函数的 __name__、__doc__等属性发生改变,如下:

1
2
3
4
5
6
7
8
9
10
11
def add_bye_world(func):
def wrapper():
func()
print("Bye World!")
return wrapper

@add_bye_world
def say_hello_world():
print("Hello World!")

print(say_hello_world.__name__)

运行结果为:

wrapper

可以看到原本名为 say_hello_world 的函数变为了 wrapper,如果我们要保留原函数的名称等属性,需要通过使用 Python 自带模块 functools 中的 wraps 来保留函数的元信息,相应的在装饰器定义处加入一行代码就可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import wraps

def add_bye_world(func):
@wraps(func) ### 这里为新增代码
def wrapper():
func()
print("Bye World!")
return wrapper

@add_bye_world
def say_hello_world():
print("Hello World!")

print(say_hello_world.__name__)

运行结果为:

say_hello_world

4.Python 中一些常见的装饰器

classmethod、staticmethod

classmethodstaticmethod 都是在类中定义方法时使用,它们的作用都是将类中的实例化方法转化为类方法,其中实例化方法的意思是需要将类实例化之后才能调用,而类方法的意思是在调用的时候,我们并不需要将类实例化,而是可以直接通过类来调用。二者同时也存在一定的差别,其中 classmethod 会接收该类作为第一个隐参,通常为 cls,而 staticmethod 不会。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SayHello:
s1 = 'Dog'
s2 = 'Cat'

def say_hello_1(self):
print("Hello {}!".format(self.s1))

@classmethod
def say_hello_2(cls):
print("Hello {}!".format(cls.s2))

@staticmethod
def say_hello_3(s3):
print("Hello {}!".format(s3))

SayHello.say_hello_2()
SayHello.say_hello_3("World")
say_hello = SayHello()
say_hello.say_hello_1()

运行结果为:

Hello Cat!

Hello World!

Hello Dog!

其中 say_hello_2、say_hello_3 为类方法,可以直接通过类来调用,而 say_hello_1 为实例方法,需要将类实例化后调用,直接通过类调用会报错。

参考资料:

  1. https://docs.python.org/zh-cn/3/library/functions.html#classmethod
  2. https://docs.python.org/zh-cn/3/library/functions.html#staticmethod

property

property 装饰器同样通常在类方法定义中使用,其作用是创建一个与方法同名只读属性,常用作读取类定义中的私有变量,如下:

1
2
3
4
5
6
7
8
9
10
class Animals:
def __init__(self, animal_name):
self.__name = animal_name

@property
def animal_name(self):
return self.__name

animal_dog = Animals('Dog')
print(animal_dog.animal_name)

运行结果为:

Dog

可以看到这里由于我们使用了 @property 装饰器,导致创建了一个 animal_name 的属性,这个属性返回的是类中私有变量 __name 的值,我们可以在类实例化之后直接调用该属性,这里需要注意的是:调用的时候不要加括号,因为这里我们已经将相应的方法转化为属性,而不是方法。另外,如果我们直接访问实例的私有变量 __name,程序将会抛出错误 AttributeError

参考资料:

  1. https://zhuanlan.zhihu.com/p/141665827

abstractmethod

同样通常在方法定义中使用,其作用是指定抽象基类中的抽象方法,抽象基类主要有两个特点:

  • 抽象基类不能被实例化,只能被继承
  • 子类必须实现抽象基类里定义的抽象方法,否则不能被实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from abc import ABCMeta, abstractclassmethod

class Animals(metaclass=ABCMeta):
def __init__(self, animal_name):
self.__name = animal_name

@abstractclassmethod
def print_animal_name(self):
pass

class Dog(Animals):
def __init__(self):
self.__name = 'Dog'

def print_animal_name(self):
print("Animal: Dog")

a_dog = Dog()
a_dog.print_animal_name()

运行结果为:

Animal: Dog

上面代码中,Animals 为抽象基类,其中 print_animal_name 这一方法为抽象方法,因此在之后所有的继承该类的类中,都需要在相应的类中实现该方法,否则类将不能实例化,这里 Dog 类就是继承自该抽象基类。

有关抽象基类和抽象方法的详细解释可以参考以下几篇文章:

  1. http://106.14.160.45:8080/part05_core_topic/chapter03/article0302/
  2. https://zhuanlan.zhihu.com/p/360144699