python闭包与引用以及需要注意的陷阱

1. 闭包的概念

闭包(closure)是一种函数,它可以访问其外部函数作用域中的变量,并且在函数返回后仍然可以访问这些变量。它可以看作是一个函数和执行该函数的环境的组合体。

闭包函数通常返回一个内部函数,而内部函数可以访问闭包函数中的变量,这些变量在闭包函数的上下文中被“捕获”并保存。这种“捕获”变量的机制称为“闭包”。

以下是一个简单的闭包函数的例子:

def outer():

x = 10

def inner():

print(x)

return inner

closure = outer()

closure() # 输出 10

在这个例子中,outer函数返回了一个内部函数inner。在执行outer函数的过程中,变量x被“捕获”并保存。因此,在执行closure函数时,可以访问到变量x的值。

2. 引用的概念

在Python中,变量是引用(reference)。当我们给一个变量赋值时,实际上是把值对象的引用赋给变量。当我们修改一个变量的值时,实际上是修改了值对象而不是变量本身。

以下是一个示例:

a = [1, 2, 3]

b = a

b.append(4)

print(a) # 输出 [1, 2, 3, 4]

在这个例子中,变量a是一个列表对象[1, 2, 3]的引用。变量b被赋值为a,因此b也是同样的列表对象的引用。当我们对b进行修改时,实际上是修改了这个列表对象,因此a也会受到影响。

3. 闭包中的引用陷阱

由于Python中变量的引用机制,在闭包函数中可能会出现一些意料之外的结果。

3.1 缺陷:变量绑定到函数的默认值时被修改

在Python中,一般会把可变对象(如列表)作为函数的默认值,这样可以避免每次调用函数时都创建一个新的对象。

但是,当闭包函数引用这个默认值时,就会出现问题:

def outer():

inner_list = []

def inner(val):

inner_list.append(val)

print(inner_list)

return inner

closure1 = outer()

closure1(1) # 输出 [1]

closure2 = outer()

closure2(2) # 输出 [2]

closure1(3) # 输出 [1, 3]

在这个示例中,outer函数返回了一个能够向inner_list中添加元素的闭包函数inner。但是,由于inner_list是一个可变对象(列表),它被绑定到了inner函数的默认值。

当我们分别用closure1和closure2两个函数各自添加元素时,我们发现inner_list被同时修改了。这是因为inner_list被绑定到了inner函数的默认值中,在每次调用inner函数时,inner_list的引用并没有被重新绑定到一个新的空列表,而是指向了同一个列表对象。

要避免这个问题,可以在闭包函数中使用不可变对象(如元组)或者使用None作为默认值,然后在函数内部创建一个新的可变对象。

3.2 缺陷:变量被后续引用改变

当一个闭包函数被作为参数传递给另一个函数时,有时会出现变量被后续引用改变的问题。

def outer():

inner_list = []

def inner(val):

inner_list.append(val)

print(inner_list)

return inner

def call_func(func):

func(1)

closure = outer()

call_func(closure) # 输出 [1]

closure(2) # 输出 [1, 2]

在这个示例中,outer函数返回能够向inner_list中添加元素的闭包函数inner。然后,我们又定义了一个call_func函数,它接受一个函数作为参数,并且调用这个函数。

当我们把closure函数作为参数传递给call_func函数时,第一次调用closure函数时,列表inner_list中被添加了元素1。但是当我们在闭包函数外部再次调用closure函数时,inner_list被修改为了一个新的空列表。

这是因为Python中,函数的默认值是在函数定义的时候计算的,而不是在函数运行的时候计算的。因此,inner_list被绑定到了closure函数的默认值中,在第一次调用closure函数时,inner_list的引用并没有被重新绑定到一个新的空列表,而是指向了同一个列表对象。而在第二次调用closure函数时,inner_list被重新绑定到了一个新的空列表。

为了避免这个问题,可以创建一个包含inner_list的元组,并将元组作为闭包函数的默认值。这样,inner_list的引用就不会被改变。

4. 总结

本文介绍了Python中闭包的概念和使用方法,并且讨论了在闭包函数中可能出现的引用陷阱。为了避免这些陷阱,我们可以使用不可变对象作为默认值,或者使用元组来封装需要修改的对象。

后端开发标签