深盾安全
闭包机制介绍
闭包(Closure)是一个能够访问其自身定义时的词法作用域中的变量,即使该函数是在其词法作用域之外被执行的函数。
核心特性
基本特征
要创建一个闭包,必须同时满足以下三个条件:
嵌套函数
在函数内部定义另一个函数
引用外部变量
内部函数可以访问外部函数的变量
返回内部函数
外部函数执行完毕后,内部函数仍然可以访问外部函数的变量
适用场景
数据封装
可以隐藏内部状态
函数工厂
创建具有特定行为的函数
装饰器
修改函数行为
回调函数
保持上下文信息
关键要点
变量作用域
内部函数可以访问外部函数的变量,但不能直接修改,如果想要修改需要使用nonlocal关键字
延迟绑定
在循环中创建闭包时要注意变量捕获的时机
内存管理
闭包会保持对外部变量的引用,可能影响垃圾回收
性能考虑
闭包会占用额外的内存来存储捕获的变量
简单示例
示例代码
以经典的计数器闭包为例,参考代码如下:
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
# 创建两个独立的计数器
counter1 = create_counter()
counter2 = create_counter()
print(f"第一个counter1的值: {counter1()}")
print(f"第一个counter1的值: {counter1()}")
print(f"第二个counter2的值: {counter2()}")
print(f"第一个counter1的值: {counter1()}")
示例详解
- 嵌套函数
create_counter
作为外部函数,其内部定义了 counter
函数。
- 引用了外部变量
counter
函数引用了外部函数 create_counter
的局部变量 count
。
- 返回内部函数
create_counter
返回了 counter
函数对象.
- 记忆效应
当 create_counter
执行完毕后,其局部作用域本应被销毁。但由于 counter
引用了 count
变量,Python
会将这个变量 count
的生存期与闭包函数 counter1绑定
,使其持续存在。每次调用 counter1
,它操作的都是它记住的那个 count
变量。
- 独立性
创建两个计数器create_counter
时,他们的值是独立存在的,两者是互不干扰。
示例结果
执行上述示例,输出结果如下:
由此可以看到每次调用 create_counter()
都会创建一个全新的、独立的闭包实例,它们拥有各自独立的 count
变量,输出的也是自己独立的count
状态。
nonlocal关键字
在 Python
中,如果内部函数只是读取外部变量的值,不需要 nonlocal
。但如果需要修改它,则必须使用 nonlocal
关键字明确声明该变量不是内部函数的局部变量,而是来自外部嵌套作用域。
没有 nonlocal
的情况,在写到count = count + 1
时,就会提示count
引用失败的错误。
def no_counter():
count = 0
def counter():
count = count + 1
return count
return counter
counter1 = create_counter()
__closure__属性
Python 为每个函数对象提供了一个 __closure__
属性,可以通过它来查看闭包的内部情况。
- 如果一个函数是闭包,
__closure__
属性会返回一个由单元格对象组成的元组。 - 如果不是闭包,则该属性为
None
。 - 单元格对象有一个
cell_contents
属性,可以获取到它存储的实际值。
ef test_func(x):
def inner_func(y):
return x + y
return inner_func的值
closure_value = test_func(3)
print(closure_value.__closure__) # 输出单元格对象
print(closure_valueclosure_value.__closure__[0].cell_contents) # 输出存储的实际值
print(closure_value(5)) # 输出inner_func的值
结果如图所示:
常见应用场景
装饰器
装饰器本质上就是一个接收函数作为参数并返回一个闭包的高阶函数,是使用闭包最常见的场景,参考示例如下:
def my_decorator(func):
def wrapper():
print("function1")
func()
print("function2")
return wrapper # 返回一个闭包
@my_decorator
def hello():
print("Hello World!")
print(hello())
函数工厂
实现函数功能可以根据不同的参数,生成配置不同的函数,通过闭包,可以将变量"隐藏"起来,只能通过特定的函数进行访问和修改,相当于模拟了面向对象中的封装性,参考示例如下:
def fun_factory(exponent):
def power(base):
return base ** exponent
return power
square = fun_factory(2) # 生成一个计算平方的函数
cube = fun_factory(3) # 生成一个计算立方的函数
print(square(4)) # 16
print(cube(4)) # 64
安全防范
由于Python是一个面向字符串流的解释执行的特点,会引发了安全问题,使用者执行Python程序时, python解释器需要通过Python源码来解释执行,即便是编译成pyc,也有工具可以直接反编译回Python代码,因此保护Python脚本变得尤为关键。
Virbox Protector工具支持对py脚本进行字节码级别保护,可以防止源码泄露,参考文档Python程序保护最佳实践。