1. 闭包 (Closure)
核心概念
闭包并非 Python 独有,Scala 中也有。 简单来说:闭包 = 函数 + 该函数定义时的环境(上下文变量)。
这就好比 Spark 中,你在 Driver 端定义了一个变量,然后在 RDD 的 map 算子中使用它。Spark 会把这个变量序列化并“打包”发送到各个 Executor 节点上。这个“打包带走”的动作,就是闭包的核心逻辑。
Python 代码实战
Python 的函数是“一等公民”(First-class citizen),可以嵌套定义。
def make_normalizer(mean, std):
# — 外部环境 (Environment) —
# 这两个变量被内部函数\”捕获\”了
def normalize(x):
# — 核心逻辑 (Code) —
# 即使 make_normalizer 已经执行完毕返回了,
# normalize 依然记得 mean 和 std 的值
return (x – mean) / std
return normalize
# 创建一个针对 ImageNet 数据的归一化函数
# 此时,mean 和 std 被\”封印\”在了 func 对象的 __closure__ 属性中
imagenet_norm = make_normalizer(mean=0.485, std=0.229)
# 像调用普通函数一样调用
print(imagenet_norm(0.5))
# 【硬核视角】大数据开发者喜欢看底层:
# 我们可以查看闭包里到底存了什么
print(imagenet_norm.__closure__[0].cell_contents) # 输出 0.485 (mean)
❗ Java/Scala 差异点 (Diff):
-
Java: 匿名内部类或 Lambda 也可以访问外部变量,但要求变量必须是 final 或 effectively final(不可变)。
-
Python: 只有 nonlocal 关键字限制。你可以读取外部变量;如果你想修改闭包内的外部变量(比如计数器),需要在内部函数声明 nonlocal count。
-
Scala: Scala 的闭包行为和 Python 非常像,都是通过捕获引用实现的。
关键字Nonlocal
Nonlocal关键字修饰闭包内层的函数时, 使其可以改变外层函数的变量引用.
在 Python 中,使用规则可以总结为一句话:读(Read)和修改内部(Mutate)不需要声明,重指向(Reassign)必须声明。
1. 什么时候【不需要】nonlocal?
这是最常见的情况。只要你不使用赋值符号 = 修改变量本身的指向,就不需要。
情况 A:只是“读取”外部变量
Python 遵循 LEGB 规则(Local -> Enclosing -> Global -> Built-in)。它会自动向外查找。
def outer():
x = 100


