Python 中的闭包

Python 中的闭包

问题

假设我们有一个log函数用于向终端输出一条信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
def log(msg):
print(msg)

def task1():
log('processing task')
...

def task2():
log('processing task')
...

task1() # processing task
task2() # processing task

两个函数task1,tasks2都调用了log函数输出了相同的log信息。
让我们很难定位到底哪一个是task1输出的,哪一个是task2输出的。
那怎么办?

显然我们可以给log信息添加一个前缀信息,例如:

1
2
[task1]: processing task
[task2]: processing task

实现方法

方案 1

我们可以修改log函数签名,增加一个参数prefix。但是这么做的问题是,log函数的签名被修改了,那么所有引用该函数的地方都需要修改,对于大型工程项目会造成相当大的工作量。

方案 2

使用闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def log(msg):
print(msg)

def log_generator(prefix):
def wrapper(msg):
log('[{}]: {}'.format(prefix, msg))
return wrapper

log1 = log_generator(prefix='task1')
log2 = log_generator(prefix='task2')

def task1():
log1('processing task')
...

def task2():
log2('processing task')
...

task1() # [task1]: processing task
task2() # [task2]: processing task

如以上代码所示,我们通过log_generator可以生成拥有任意前缀的信息的log函数,而不改变log函数本身的签名。
这个技巧或者概念被称为闭包

闭包概念

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.

在编程语言中,一个闭包,也被称为语法闭包或者函数闭包,是一种在支持头等函数的语言中实现静态范围命名绑定的技巧。闭包是一种结构体,存储一个函数以及相关的环境。该环境是一个映射关系,把函数里面自由变量(本地使用,但是在外部定义)与闭包创建时变量名对应的值或者引用绑定在一起。

简单来说,闭包就是一个函数和一个“字典”,该“字典”将闭包创建时函数所引用的_外部变量_变成键值对。

1
2
3
4
5
6
7
8
9
10
def log(msg):
print(msg)

def log_generator(prefix):
def wrapper(msg):
log('[{}]: {}'.format(prefix, msg))
return wrapper

log1 = log_generator(prefix='task1')
log2 = log_generator(prefix='task2')

log_generator函数为例,在创建log1时,我们创建了一个闭包,该闭包有一个函数wrapper和一个“字典”组成,该字典有一个键值对prefix -> task1。同理在log2闭包的字典里,有一个键值对prefix -> task2

应用场景

装饰器

1
2
3
4
5
6
7
8
9
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

def func(msg):
print(msg)

func = decorator(func)

如上述样例代码所示,我们可以利用decorator函数做任何事情,例如打印 log,记录信息,修改参数等等。可以再不修改原有函数的情况下,增加很多有用的信息。

1
2
3
4
5
6
7
8
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

@decorator
def func(msg):
print(msg)

Python 使用语法糖@decorator帮助减少最后一条赋值代码。