Python函数框架:外框架、内框架与环境模型
一、引言
当你在 Python 中调用一个函数时,解释器在背后做了大量工作来管理变量的"可见性"。为什么函数内部能访问全局变量,但全局却不能直接看到函数内部的变量?为什么嵌套函数能记住外层函数的变量?这一切的答案,都指向一个核心概念——环境模型(Environment Model)。
本文源自 UC Berkeley CS61A 课程的核心内容,带你从"框架(Frame)"的视角理解 Python 的函数调用机制。
二、什么是环境模型
环境模型是 Python 解释器用来追踪变量名与值之间绑定关系的一套机制。它的核心思想极其简单:
一个表达式在特定环境中被求值。环境由一系列框架(Frame)组成,每个框架包含一组绑定(Binding)——即变量名到值的映射。
在这套模型中,有两种最关键的结构:
- 全局框架(Global Frame):程序启动时就存在的唯一框架,存储全局变量和函数定义
- 局部框架(Local Frame):每次函数调用时动态创建的新框架,存储函数的形参和局部变量
三、框架是什么
框架本质上是一个上下文(Context),记录着"在这个范围内,哪些名字指向哪些值"。你可以把它想象成一张表格:
| 名字(Name) | 值(Value) |
|---|---|
| x | 10 |
| square | func square(x) {...} |
当你引用一个名字时,Python 从当前框架开始查找;如果找不到,就顺着"父框架"的指针向外查找,直到全局框架。如果在全局框架也找不到,就会抛出 NameError。
四、外框架与内框架
这是理解环境模型的关键区分:
外框架(Outer Frame / Enclosing Frame)
外框架是函数定义时所在的那个环境。换句话说,函数"记住"了自己是在哪里被定义的,后续所有变量查找都会从那里开始向外延伸。
1 | x = 10 # 全局框架中 x = 10 |
这里 inner 是在 outer 的内部定义的,所以 inner 的外框架就是 outer 的局部框架。当 inner 中引用 x 时:
- 先在
inner自己的局部框架中找 —— 没有 - 再去外框架(
outer的局部框架)找 —— 找到x = 20
所以输出是 20,而不是全局的 10。这就是**词法作用域(Lexical Scope)**的核心规则:查找路径由函数定义的位置决定,而不是调用的位置。
内框架(Inner Frame / Local Frame)
内框架就是当前函数调用创建的新框架。每次调用函数,哪怕是同一个函数,都会创建一个全新的局部框架:
1 | def add_n(n): |
add_n(3) 调用时,创建了一个局部框架,里面 n = 3。这个框架被返回的 inner 函数"记住"了。add_n(5) 再次调用时,创建了另一个完全独立的局部框架,里面 n = 5。两次调用产生了两个互不干扰的"记忆"——这就是闭包的本质。
五、环境模型的查找规则
整个环境是一个框架链表。每次查找变量时,Python 遵循这样的路径:
1 | 当前局部框架 → 外框架(定义时环境) → 更外层框架 → ... → 全局框架 |
这解释了下面这段代码的行为:
1 | x = "global" |
以及:
1 | x = "global" |
六、可视化:从环境图理解嵌套函数
1 | x = 1 |
这段代码的环境图如下:
1 | 全局框架: |
注意关键点:inner 函数对象保存了一个指向 outer(5) 调用框架 的引用——这就是它的外框架。即使 outer(5) 已经执行完毕,这个框架依然不会被销毁,因为 inner 还"抓着"它不放。
七、与 C++ 的对比
| 概念 | Python(环境模型) | C++ |
|---|---|---|
| 变量查找 | 框架链,从内向外 | 块作用域,从内向外 |
| 函数内访问外部变量 | 通过外框架引用 | 通过捕获列表(lambda)或直接可见 |
| 闭包实现 | 函数对象持有外框架引用 | lambda 捕获列表拷贝/引用 |
| 全局变量 | global 关键字声明后赋值 | 直接可见,用 :: 区分 |
| 生命期管理 | GC,有引用就不销毁 | 栈上自动销毁,堆上手动管理 |
C++ 的 lambda 需要显式声明捕获列表([=] 或 [&]),而 Python 的闭包自动持有整个外框架——这带来了灵活性,但也意味着需要注意闭包变量的生命期。
八、常见陷阱:循环中的闭包
这是环境模型最经典的陷阱:
1 | funcs = [] |
原因:lambda 的外框架就是全局框架(或包含 for 循环的函数框架),它引用的是变量名 i,而不是变量值。当 lambda 最终被调用时,i 已经变成了 2。
修复方式——利用默认参数在定义时"快照"值:
1 | funcs = [] |
九、总结
环境模型是 Python 函数作用域的底层逻辑,掌握它意味着你真正理解了三件事:
- 外框架是函数"出生"的地方,决定了它能看见哪些变量。这是词法作用域的核心。
- 内框架是函数"执行"的地方,每次调用都全新创建,互不干扰。
- 环境是一串框架的链条,变量查找沿着内→外的方向逐级回溯,直到全局。
理解了框架与外框架的关系,闭包不再神秘,嵌套函数不再困惑,nonlocal 和 global 的语义也变得理所当然。环境模型不是黑魔法,而是精心设计的、一致的名字查找规则。

