Python 装饰器

概述

   在 Python3某些强大的特性与Lisp的关系 一文中提到 Python 装饰器提供了一种定义高阶函数的语法。从定义上说,装饰器是一个以另一个函数为参数的函数,它将隐式地扩展或修改这个函数的行为。

主要翻译自:Primer on Python Decorators by Geir Arne Hjelle

函数

注意: Python 尽管不是纯粹的函数式编程语言(如与 Lisp 相比),但仍然支持许多函数式编程的概念,包括函数作为第一级状态对象(first-class objects)。(这与 python 的 everything is a object 也是保持一致的。)

###1.1 First-Class Objects
函数作为第一级对象意味着 函数可以被作为参数传递,就如其他对象(如 stringfloat, list等)一样。示例如下:

1
2
3
4
5
6
7
8
def say_hello(name):
return f"Hello {name}"

def be_awesome(name):
return f"Yo {name}, together we are the awesomest!"

def greet_bob(greeter_func):
return greeter_func("Bob")

say_hello()be_awesome 是普通的函数,而greet_bob() 函数将一个函数作为它的参数,例如我们可以将 say_hello 的引用传递给它:

1
2
>>> greet_bob(say_hello)
'Hello Bob'

在 Python 中,函数的参数都是引用(具体实现可参考 Python Internals),say_hello 作为greet_bob的参数时不会被执行,它只会在 greet_bob 的函数体中被执行。

Inner 函数

Inner函数是定义在函数中的函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
def parent():
print("Printing from the parent() function")

def first_child():
print("Printing from the first_child() function")

def second_child():
print("Printing from the second_child() function")

second_child()
first_child()

first_child()second_child() 作为 parent() 的内部函数,它们存在于parent()函数的内部域中,也就是说,如果在parent()外调用它们则会失败:

1
2
3
4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'first_child' is not defined

函数作为返回值

函数可以作为返回值,例如:

1
2
3
4
5
6
7
8
9
10
11
12
def parent(num):
def first_child():
return "Hi, I am Emma"

def second_child():
return "Call me Liam"

if num == 1:
return first_child
else:
return second_child

parent() 函数返回的是first_child()second_child()函数的引用,如果要调用它们,需要加’()’,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> first = parent(1)
>>> second = parent(2)

>>> first
<function parent.<locals>.first_child at 0x7f599f1e2e18>

>>> second
<function parent.<locals>.second_child at 0x7f599dad5268>

>>> first()
'Hi, I am Emma'

>>> second()
'Call me Liam'

简单的装饰器

先看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

def say_whee():
print("Whee!")

say_whee = my_decorator(say_whee)

输出:

1
2
3
4
>>> say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

say_whee() 函数的引用作为 my_decorator() 参数传入,my_decorator()函数返回其内部函数 wrapper() 的引用,并赋值给 say_whee。因此,在全局环境中,say_whee标识符不再绑定为 say_whee() 函数,而是绑定为 wrapper()函数。然而,wrapper() 函数在它的上下文环境中保存了原来的 say_whee() 函数的引用。(在这里,过程和数据的概念不再是完全对立的,过程可以是数据,数据也可以是过程。)

简单来说,装饰器包装了一个函数,修改了它的行为。

语法糖

上述装饰 say_whee() 函数的做法是有些繁琐的,所以在 Python 中允许使用 @符号来简单地使用装饰器,下面的例子做了同样的事情,但更加简洁:

1
2
3
4
5
6
7
8
9
10
11
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

@my_decorator
def say_whee():
print("Whee!")

重用装饰器

装饰器函数可以单独定义在一个模块中,其他模块可以通过 import 导入它。

带有参数的装饰器

在内部包装函数中使用 ** *args ** 和 ** *kwargs **作为形参,使得它可以接受任意的位置参数和关键字参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper_do_twice

@do_twice
def say_whee():
print("Whee!")

@do_twice
def greet(name):
print(f"Hello {name}")

测试:

1
2
3
4
5
6
7
8
>>> say_whee()
Whee!
Whee!

>>> greet("World")
Hello World
Hello World

从装饰器函数中返回值

如果被装饰的函数有返回值,我们只需要在包装函数中返回这个返回值即可。如下所示:

1
2
3
4
5
6
7
8
9
10
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice

@do_twice
def return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
1
2
3
4
>>> return_greeting("Adam")
Creating greeting
Creating greeting
'Hi Adam'

使用 @functools.wraps()

更有用的例子