python中的闭包

从踩坑中学习到python闭包

在搭建接口自动化框架的时候踩了一个坑。

1 场景

在循环中定义了一个函数作为动态创建函数的模板,通过循环将测试用例数据作为参数传入到该函数当中去执行。

2 问题

每次函数运行时的参数都是最后一次循环时传入的测试用例参数

3 原因

通过排查,由于函数是在循环内部定义的,所以所有的函数都引用了同一个测试用例数据变量(假如是A,引用的是变量地址,而不是变量的值),所以只会取循环结束时A的值。这是一个常见的 Python 闭包问题。

4 错误代码实例

将该问题抽象如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestCase:
pass

def closure():
l = [1, 2, 3]
for i in l:
def functionTemplate():
print(i)
setattr(TestCase, "test"+str(i), functionTemplate)

if __name__ == '__main__':
closure()
# 获取MyClass类的所有属性名
attr_names = dir(TestCase)
# 过滤出方法名(排除非方法属性和内建方法)
method_names = [attr for attr in attr_names if callable(getattr(TestCase, attr, None)) and not attr.startswith("__")]
# 打印方法名
print(method_names)
# 调用函数,打印i的值
TestCase.test1()
TestCase.test2()
TestCase.test3()

在上述代码中,在循环内定义了一个方法,作为反射时用到的属性值,向TestCase类中注入方法,本来的目的是想每个方法内输出不同的i值。但是结果都输出了最后一次循环的取值。

1
2
3
4
5
# 这是上面代码的控制台输出结果
['test1', 'test2', 'test3']
3
3
3

5 解决方法

用嵌套函数来捕获循环中的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TestCase:
pass

def closure():
l = [1, 2, 3]
for i in l:
def func(i):
def functionTemplate():
print(i)
return functionTemplate
setattr(TestCase, "test"+str(i), func(i))

if __name__ == '__main__':
closure()
# 获取MyClass类的所有属性名
attr_names = dir(TestCase)
# 过滤出方法名(排除非方法属性和内建方法)
method_names = [attr for attr in attr_names if callable(getattr(TestCase, attr, None)) and not attr.startswith("__")]
# 打印方法名
print(method_names)
# 调用函数,打印i的值
TestCase.test1()
TestCase.test2()
TestCase.test3()
1
2
3
4
5
# 控制台输出结果如下
['test1', 'test2', 'test3']
1
2
3

6 闭包

闭包(Closure)是编程中的一个重要概念,特别是在函数式编程中。闭包可以简单理解为能够记住并访问其词法作用域(lexical scope)的变量(即定义在其外部作用域而非全局作用域的变量)的函数。即使这个函数在其外部作用域被执行完并且返回之后,它仍然能够访问那些变量。

以下是闭包的一些关键点:

  1. 函数内部函数:闭包通常在一个函数内部定义另一个函数。
  2. 作用域链:内部函数可以访问其外部函数的变量,即使外部函数已经执行完毕。
  3. 返回内部函数:外部函数返回内部函数,使得内部函数可以在外部函数外部被调用,并且依然能够访问外部函数的变量。
  4. 延长变量生命周期:由于闭包的存在,那些原本在外部函数执行完毕后就应该被销毁的变量得以保存下来,从而延长了这些变量的生命周期。

闭包的使用场景包括:

  1. 装饰器:在不修改原有函数代码的情况下,增加额外的功能。

  2. 回调函数:封装了状态的函数可以作为回调函数传递给某些操作。

  3. 函数工厂:根据输入参数的不同返回不同行为的函数。

简单的闭包代码示例

1
2
3
4
5
6
7
8
9
10
def outer_function(outer_variable):  
def inner_function():
print(outer_variable) # 访问外部函数的变量
return inner_function # 返回内部函数

# 调用外部函数,并将返回的内部函数赋值给变量func
func = outer_function("Hello from outer function!")

# 调用内部函数,此时外部函数已经执行完毕,但内部函数仍然可以访问outer_variable
func() # 输出:Hello from outer function!

在这个例子中,outer_function 是一个外部函数,它定义了一个变量 outer_variable 和一个内部函数 inner_functioninner_function 能够访问 outer_variable,这是因为它在 outer_function 的作用域内被定义。当 outer_function 被调用时,它返回 inner_function,此时 inner_function 就形成了一个闭包,因为它能够记住并访问 outer_variable 的值,即使 outer_function 已经执行完毕。

------------- End -------------