Python递归机制依赖全局变量查找 嵌套闭包成规避重命名劫持关键

【python】我调用的我不是我?你绝对不知道的python递归陷阱 — 码农高天

码农高天

Python函数在递归调用自身时通过LOAD_GLOBAL指令解析全局命名空间,外部对函数名的重绑定会直接瓦解内部递归,开发者须通过闭包将调用锁定在私有作用域中。

AI-assisted summary of 【python】我调用的我不是我?你绝对不知道的python递归陷阱 — original source. Check the original for full context.

Source published:

Key ideas

  • Python 函数内部调用同名函数时并非直接引用自身对象,而是通过 LOAD_GLOBAL 指令在全局作用域查找该名字,导致函数行为易受外部全局变量修改的影响。 (3:00)
  • 列表推导式在 Python 3 中拥有独立的匿名函数作用域(使用 STORE_FAST/LOAD_FAST),不会改变模块级变量;而模块最外层的显式 for 循环使用 STORE_NAME,会直接覆盖全局变量,导致两者调用同名函数的输出结果产生显著差异。 (0:00)
  • 当全局递归函数被重新赋值(如 fib = g)后,即使通过别名变量调用原函数,其内部递归仍会查找全局名称,进而调用被篡改后的新逻辑。 (5:00)
  • 利用闭包(Closure)在函数内嵌套定义私有递归函数,可促使字节码采用 LOAD_DEREF 解析名称,彻底切断对全局命名空间的依赖以规避作用域污染。 (5:00)

Detailed notes

## Background 该视频由技术博主“码农高天”于2024年4月12日发布在YouTube平台上。视频简介指出,这是一次关于Python递归陷阱的深入探讨,旨在揭示“你以为的递归可能并不是你以为的递归”这一反直觉现象。

## TL;DR 高天通过一段极简代码演示了反常现象:列表推导式调用函数属性与显式for循环的输出完全不一致,前者连续打印8个7,而后者正常输出0到7。通过深入Python字节码,他揭示了函数内部调用同名自身时并非引用自身对象,而是通过LOAD_GLOBAL指令向全局作用域查找名称的底层机制。基于这一特性,他剖析了递归函数若在全局被重写将导致递归错乱的严重风险,并给出了利用嵌套闭包(LOAD_DEREF)隔离全局作用域的安全解决方案。

## Atmosphere & style 视频风格硬核严谨且充满解谜般的戏剧性,开篇抛出看似等价却输出迥异的极简代码,迅速抓住观众的好奇心与困惑感。随着字节码指令的层层拆解,叙述节奏由浅入深,高天以敏锐而冷静的工程师视角带领观众直击底层机制的“可怕真相”。整期内容既有发现诡异Bug时的烧脑与震撼,又有揭开Python底层作用域面纱后的豁然开朗。

## Bulletin - [03:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=180s) Python 函数内部调用同名函数时并非直接引用自身对象,而是通过 LOAD_GLOBAL 指令在全局作用域查找该名字,导致函数行为易受外部全局变量修改的影响。 - [00:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=0s) 列表推导式在 Python 3 中拥有独立的匿名函数作用域(使用 STORE_FAST/LOAD_FAST),不会改变模块级变量;而模块最外层的显式 for 循环使用 STORE_NAME,会直接覆盖全局变量,导致两者调用同名函数的输出结果产生显著差异。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 当全局递归函数被重新赋值(如 fib = g)后,即使通过别名变量调用原函数,其内部递归仍会查找全局名称,进而调用被篡改后的新逻辑。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 利用闭包(Closure)在函数内嵌套定义私有递归函数,可促使字节码采用 LOAD_DEREF 解析名称,彻底切断对全局命名空间的依赖以规避作用域污染。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=0s) 在同一作用域通过循环动态定义同名函数并给其绑定属性时,各函数内部访问同名属性的行为容易产生反直觉结果。 - [00:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=0s) 列表推导式 [f.index for f in FUNCTIONS] 能够正常输出 0 到 7,但执行 [f() for f in FUNCTIONS] 时却会反常地连续输出 8 个 7。 - [00:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=0s) 将列表推导式改写为最外层的显式 for 循环遍历调用 f(),输出结果又会从 8 个 7 恢复为 0 到 7。 - [03:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=180s) 字节码分析表明,函数内部执行 return f.index 时并非自引用,而是触发 LOAD_GLOBAL f 指令前往最外层全局命名空间查找。 - [03:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=180s) Python 3 的列表推导式运行在独立的局部作用域中,内部变量更新不会污染全局变量 f,使得全局 f 始终停留在循环终态(index=7)。 - [03:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=180s) 显式 for 循环运行在模块顶层,每次迭代均执行 STORE_NAME 实时覆盖全局变量 f,导致函数调用每次都读取到当次循环对应的对象。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 递归函数的自调用高度依赖全局变量名解析,一旦全局同名变量被覆盖,原函数内部的递归步骤就会转向新逻辑。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 若将递归函数赋值给别名 F 并将原函数名重新指向返回 0 的函数,调用 F 将无法正常完成原递归,而是返回 0。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 防范全局递归陷阱的有效策略是使用闭包,在外层函数内部定义私有递归函数并通过 LOAD_DEREF 实现安全自调用。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 闭包结构能够将递归调用封闭在局部命名空间中,外部即使在全局声明同名变量也无法干扰内部递归逻辑。 - [05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 理解 Python 字节码层面的作用域规则,是排查推导式与显式循环不一致等深层隐蔽 Bug 的核心关键。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=0s) 函数内的同名引用与反直觉的输出结果

码农高天在开篇抛出了一个关于递归的“坑”,并用一段极其精简但行为诡异的代码来展示其发现的根源。他首先创建了一个空列表 `FUNCTIONS = []`,然后写了一个简单的 `for` 循环(循环 8 次)。在每次循环中,都定义了一个新的函数 `f()`,其内容只有一行:`return f.index`。虽然普通的 Python 函数默认没有 `index` 属性,代码检查工具(如 mypy)也会给出警告提示函数没有该属性,但他直接忽略警告,强行给新定义的函数对象绑定属性 `f.index = n`,并把每个函数对象 `f` 添加到 `FUNCTIONS` 列表中。按照直觉,这个列表里保存了 8 个函数对象,它们的 `.index` 属性应该依次是 0 到 7。

```mermaid flowchart TD accTitle: List Comprehension 与显式 For 循环的行为差异 accDescr: 展示为何 List Comprehension 调用 f() 打印 8 个 7,而显式 For 循环打印 0 到 7。

subgraph ListComp["推导式:List Comprehension"] LC1["生成独立匿名函数作用域"] LC2["使用 STORE_FAST / LOAD_FAST 处理局部变量 f"] LC3["不改变全局变量 f(全局 f 停留在 index=7)"] LC4["调用 f() 时内部 LOAD_GLOBAL f 获取 index=7 ➜ 打印 8 个 7"] LC1 --> LC2 --> LC3 --> LC4 end

subgraph ExplicitLoop["显式循环:Explicit For Loop"] EL1["运行在模块最外层"] EL2["使用 STORE_NAME 覆盖全局变量 f"] EL3["每次循环改变全局变量 f(依次为 0 到 7)"] EL4["调用 f() 时获取当前全局 f.index ➜ 打印 0 到 7"] EL1 --> EL2 --> EL3 --> EL4 end ```

为了验证这一点,高天先写了一个列表推导式(List Comprehension):`[f.index for f in FUNCTIONS]`,结果正如预期输出了 `0` 到 `7`。然而精彩的反直觉现象紧接着发生:当把提取属性 `f.index` 换成函数调用 `f()` 时,大家原本以为 `f()` 执行 `return f.index` 同样会返回 `0` 到 `7`,实际运行结果却不可思议地打印出了 8 个 `7`。更令人困惑的是,如果把这行列表推导式改写为理论上完全等价的显式 `for` 循环:

```python for f in FUNCTIONS: print(f()) ```

这段显式循环代码的输出竟然又变回了 `0` 到 `7`。面对如此反常的执行现象,高天引导大家深入 Python 的字节码(Bytecode)去寻找真相。

- `FUNCTIONS` 列表在循环结束后存储了 8 个独立的函数对象。 - 直接读取属性 `[f.index for f in FUNCTIONS]` 输出 `[0, 1, 2, 3, 4, 5, 6, 7]`。 - 调用函数 `[f() for f in FUNCTIONS]` 却输出了 8 个 `7`。 - 改写为模块最外层的显式 `for f in FUNCTIONS: print(f())`,输出又变回了 `0` 到 `7`。

[03:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=180s) 字节码揭秘:LOAD_GLOBAL 与作用域的碰撞

要搞明白为什么调用结果完全不同,必须查看 `f` 函数编译后的字节码。高天指出,`f` 函数生成的指令非常简单,但关键在于它执行的是一条 `LOAD_GLOBAL f` 指令。也就是说,在执行 `return f.index` 时,Python 并非从内部正在调用的那个函数本身去取属性,而是直接跑到最外层的全局作用域去查找名为 `f` 的变量,再去访问它的 `index`。正如码农高天所强调的那样:“如果你脑子转的快的话你会发现一个非常可怕的事情就是当我们在一个名字叫F的函数里面使用F的时候我们并不是在使用正在被定义的F函数而是跑回了全局变量里面去寻找F。”

弄清了这层全局查找机制,两段代码的差异便迎刃而解: 1. **列表推导式(List Comprehension)**:在 Python 3 中,列表推导式本质上被编译并执行为一个独立的匿名函数作用域。在推导式内部迭代时,循环变量 `f` 使用的是局部变量操作指令 `STORE_FAST` 和 `LOAD_FAST`。因此,在推导式遍历 `FUNCTIONS` 时,模块层级的全局变量 `f` 完全没有受到影响,它依然停留在最初构造循环结束时的状态——即第 8 个函数对象(其 `index` 为 `7`)。当推导式调用各个局部函数 `f()` 时,函数内部通过 `LOAD_GLOBAL` 找到的全局变量 `f` 永远是这最后一个函数,因而连续输出了 8 个 `7`。 2. **显式的 For 循环**:由于该循环写在文件的最外层,每次循环迭代从 `FUNCTIONS` 中取出函数赋给 `f` 时,使用的是 `STORE_NAME` 指令,这会直接修改并覆盖最外层的全局变量 `f`。因此,在第 13 行的每一次显式迭代中,全局变量 `f` 都在实时更新为当前的函数对象,调用 `f()` 触发 `LOAD_GLOBAL f` 时自然每次都读取到了当次循环赋予的新全局对象,从而打印出 `0` 到 `7`。

- `f()` 内部并没有指向自身对象引用的机制,而是直接通过 `LOAD_GLOBAL f` 解析名字。 - 推导式处于独立的作用域内,`STORE_FAST` 保障局部变量不污染外部,全局 `f` 始终为循环终止时的对象(index 属性为 7)。 - 显式最外层循环的 `STORE_NAME` 每次迭代都会覆盖全局变量 `f`。

[05:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=300s) 递归陷阱与利用闭包进行安全封装

这种查找全局变量的机制带来了极其严重的隐患,即当我们写递归函数时,函数内部尝试调用自己的时候,完全可能踩中大坑。高天以最经典的斐波那契数列(Fibonacci)递归实现为例:正常定义 `def fib(n):...` 后调用 `fib(5)` 返回值是 `8`。但如果外部逻辑做了一个看似无害的操作——先把 `fib` 保存给变量 `F = fib`,然后又重新定义或者替换了全局的 `fib` 函数(例如定义了一个直接返回 0 的函数 `def g(n): return 0` 并将其赋值给 `fib = g`),此时再去执行我们以为完全保留了原算法的 `F(5)`,输出结果直接变成了 `0`。码农高天精辟地总结了这一递归困境:“也就是说在我们写地归函数的时候我们没有办法保证这个地规函数调用自己的时候调用的还是自己。” 因为原函数内部的递归步骤在解析 `fib(...)` 时依然是去查找全局作用域,而全局的 `fib` 早已被替换成了别的东西。

```mermaid flowchart TD accTitle: 递归函数在全局被篡改与闭包封装防护对比 accDescr: 展示全局递归如何被 g 函数篡改导致返回 0,以及闭包如何使用 LOAD_DEREF 锁定作用域。

subgraph GlobalRisk["全局查找风险"] F1["调用 F(5)(原 fib 函数)"] --> F2["计算 fib(4) + fib(3)"] F2 -->|"LOAD_GLOBAL fib"| F3["查找全局名称 fib"] F3 -->|"已被赋值为 g"| F4["调用 g(n) ➜ 返回 0"] end

subgraph ClosureSafe["闭包封装安全机制"] C1["外部调用 Fib(5)"] --> C2["调用内部 _fib(5)"] C2 -->|"LOAD_DEREF _fib"| C3["从外层局部闭包作用域解析"] C3 -->|"隔离全局命名空间"| C4["外部篡改 _fib 或 fib 均无法破坏内部递归"] end ```

为了彻底规避老是在全局作用域搞来搞去导致的潜在污染,高天给出了解决思路:通过作用域将递归操作封装起来。具体做法是在外层函数 `fib` 内部嵌套定义一个内部私有递归函数 `_fib`,并由外层 `fib` 返回调用 `_fib` 的结果。通过这种闭包(Closure)结构,内部函数在递归调用自身时,使用的不再是 `LOAD_GLOBAL`,而是在该闭包的作用域链内寻找(使用 `LOAD_DEREF`)。此时,即便外部代码在全局作用域同样定义了一个 `_fib = g` 并试图覆盖,内部递归也不会跑到全局去查找,函数依然能够稳定且正确地运行。

高天最后坦言,虽然视频中的代码示例为了在极短行数内凸显核心机制而显得有些刻意与极端,但全局名称污染与引用错位却是真实存在的。如果在真实工程代码中遇到列表推导式与显式 `for` 循环打印不一致的情况,很多人可能根本无法在第一时间反应过来根源所在。

- 在全局作用域篡改函数名称(如 `fib = g`),会导致原函数内部尚未完成的递归步骤调入新逻辑。 - 闭包封装能够切断对全局变量的依赖,使内部私有函数在封闭命名空间中安全递归。 - 尽管直接篡改递归函数引用的写法并不常见,但理解字节码的全局查找逻辑对于排查 Python 中微妙的作用域 Bug 极为关键。

## Notable quotes > [03:00](https://www.youtube.com/watch?v=SGEZdP6kpmg&t=180s) 当我们在一个名字叫F的函数里面使用F的时候我们并不是在使用正在被定义的F函数而是跑回了全局变量里面去寻找F。 — 码农高天

## Mentioned 码农高天, Python, mypy, 斐波那契数列

## Video description 你以为的递归不是你以为的?python在做递归的时候可能出现的陷阱!

[Source](https://www.youtube.com/watch?v=SGEZdP6kpmg)

中文简报

Python递归机制依赖全局变量查找 嵌套闭包成规避重命名劫持关键 Python函数在递归调用自身时通过LOAD_GLOBAL指令解析全局命名空间,外部对函数名的重绑定会直接瓦解内部递归,开发者须通过闭包将调用锁定在私有作用域中。 高天通过一段极简代码演示了反常现象:列表推导式调用函数属性与显式for循环的输出完全不一致,前者连续打印8个7,而后者正常输出0到7。通过深入Python字节码,他揭示了函数内部调用同名自身时并非引用自身对象,而是通过LOAD_GLOBAL指令向全局作用域查找名称的底层机制。基于这一特性,他剖析了递归函数若在全局被重写将导致递归错乱的严重风险,并给出了利用嵌套闭包(LOAD_DEREF)隔离全局作用域的安全解决方案。 Python 函数内部调用同名函数时并非直接引用自身对象,而是通过 LOAD_GLOBAL 指令在全局作用域查找该名字,导致函数行为易受外部全局变量修改的影响。 (3:00) 列表推导式在 Python 3 中拥有独立的匿名函数作用域(使用 STORE_FAST/LOAD_FAST),不会改变模块级变量;而模块最外层的显式 for 循环使用 STORE_NAME,会直接覆盖全局变量,导致两者调用同名函数的输出结果产生显著差异。 (0:00) 当全局递归函数被重新赋值(如 fib = g)后,即使通过别名变量调用原函数,其内部递归仍会查找全局名称,进而调用被篡改后的新逻辑。 (5:00) 利用闭包(Closure)在函数内嵌套定义私有递归函数,可促使字节码采用 LOAD_DEREF 解析名称,彻底切断对全局命名空间的依赖以规避作用域污染。 (5:00)

Reading summary

码农高天指出,Python 在函数内部调用同名函数时默认会退回全局作用域查找该名称,这可能导致列表推导式行为异常,并在全局名称被篡改时使递归逻辑彻底失效。 推导式与显式循环的反直觉输出差异 (0:00) 在构造包含 8 个函数的列表 `FUNCTIONS` 时,每个函数内部仅有一行 `return f.index`,并由外部赋予从 0 到 7 的 `index` 属性。直接通过列表推导式读取属性 `[f.index for f in FUNCTIONS]` 能按预期输出 0 到 7,但将其改为函数调用 `[f() for f in FUNCTIONS]` 时,实际输出结果却变成了 8 个 7。更令人困惑的是,如果将该列表推导式改写为理论上等价的最外层显式 `for f in FUNCTIONS: print(f())`,打印结果又重新变回了 0 到 7。 字节码底层:LOAD_GLOBAL 与作用域机制 (3:00) 通过反编译字节码可以发现,函数 `f` 内部执行的是 `LOAD_GLOBAL f` 指令,这意味着 Python 在执行 `return f.index` 时并非从正在调用的函数本身提取属性,而是前往最外层的全局作用域查找名为 `f` 的变量。在 Python 3 中,列表推导式拥有独立的匿名函数作用域,内部使用 `STORE_FAST` 和 `LOAD_FAST` 操作局部变量,因此全局变量 `f` 始终停留在循环结束时的状态(即 index 为 7 的第 8 个函数),导致连续输出 8 个 7。而在模块最外层的显式 `for` 循环中,每次迭代都会通过 `STORE_NAME` 覆盖全局变量 `f`,使调用 `f()` 时能实时读取到当次循环赋予的新对象,输出了 0 到 7。 递归陷阱与闭包封装的防护方案 (5:00) 这种依赖全局查找的机制对递归函数构成了隐患。以斐波那契函数 `fib` 为例,若外部先执行 `F = fib` 保存原函数引用,随后在全局将 `fib` 重新赋值为一个恒返回 0 的函数 `g`,此时调用本应保留原逻辑的 `F(5)`,输出结果会直接变成 0,因为其内部递归解析仍会调用已被替换的全局 `fib`。为了切断对全局变量的依赖,可以通过嵌套定义私有递归函数 `_fib` 的闭包结构进行封装。在闭包内,递归调用改由 `LOAD_DEREF` 指令从外层局部作用域解析,即便全局命名空间存在同名变量或被修改,内部递归逻辑也能稳定执行。 虽然极端的函数重名与引用篡改在常规开发中较少见,但高天强调,理解名称查找与字节码层面的作用域规则,是排查推导式与循环不一致等微妙 Bug 的关键依据。

Source quotes

[3:00] 码农高天: 当我们在一个名字叫F的函数里面使用F的时候我们并不是在使用正在被定义的F函数而是跑回了全局变量里面去寻找F。

Original transcript

[0:00] 这期视频我们来讲一个关于地规的坑但是在讲它之前呢我们先来聊一下我发现这个坑的起源我们来看这么一段代码首先我建了一个空List叫FUNCTIONS紧接着我做了一段非常简单的循环在每一次的循环里我都定义一个新的函数F这个函数非常简单返回f.index那当然一个正常的python函数里面是没有index这个属性的所以我们在定义完这个函数之后我们让这个函数的点 index 等于 n当然这块买派会给你个Warning说函数是没有index这个attribute的我们不要管它在做完了这件事之后我们把建立的这个函数F给放到FUNCTIONS这个list里面那么在这个循环运行之后Functions这个list里面应该有8个我们保存的函数每一个函数的点index应该是不一样的分别是0到7我们可以通过下面这种方法来验证一下我们做一个很简单的List comprehension把functions里面的每一个f拿出来然后取它的点index我们可以看到运行的结果跟我们想象的一样打印出了0到7好那到这里你可能觉得虽然代码写的有点奇怪但是这个结果很普通啊 对不对接下来精彩的部分就要来了我们把刚才的F.index变成F括号也就是我们调用这个F函数那我们知道我们的 F 函数就是 return F 点 index所以这段程序的运行结果是什么呢这段程序的运行结果打印出了8个7你知道是为什么吗如果你已经感到有点懵的话我们再来看接下来的角色代码我们把上面的list comprehension写成下面的等价形式看过我之前视频的小伙伴对这个部分肯定很熟悉了理论上说这两种形式是等价的然而如果我们运行这段代码的话你会发现输出的结果又变成了0到7那么这种反直觉的结果究竟是为什么呢答案我们肯定还要回到字节码里面去找这个字节码中最有趣的部分就是我们这个f函数编译出来的东西F函数的字节码非常简单它做了一个load global f这是什么意思呢就是他要去全局变量里面找F然后拿到这个f的index如果你脑子转的快的话你会发现一个非常可怕的事情就是当我们在一个名字叫F的函数里面使用F的时候我们并不是在使用正在被定义的F函数而是跑回了全局变量里面去寻找F这个时候我们就可以解释刚才的行为了我们之前讲过在使用List Comprehension的时候它本质上是建立了一个匿名函数我们可以看到在这个匿名函数里面他把这个 里面拿出来的东西保存进了F这个local变量因为他使用的是storefast和loadfast也就是说在循环的过程中我们并没有改变全局变量F所以当我们把局部变量F当做一个函数进行调用的时候这个F的返回值是全局变量F的index属性而全局变量F是在我们之前的循环里定义的最后一个F它的index是7于是我们就打印出了8个7而在使用显示的for loop的时候我们从Function里面拿到的每一个函数都被用StoreName保存在了F这个名字里由于我们的for loop是在整个文件的最外层,所以这个函数就被保存到了全局变量F里也就是在进行第13行这个for loop的时候我们在不停的改变全局变量f于是在每一次调用F的时候它返回的全局变量F的index也就都不一样了那么这意味着什么呢这意味着当我们使用地规函数的时候也就是我们尝试在函数里调用这个函数的时候可能会踩一个大坑我们看左边是一个非常简单的菲波那切函数也是我们在讲地龟的时候非常常 [5:00] 我们打印一个费布纳切5打出来的是8这都是正常的然而如果在这个过程中我们先把Fib这个函数保存到F里我们又替换掉了fib这个函数名让直接return的G函数变成fib我们再print F5也就是我们以为的Fib5的时候它就会打印出零因为尽管在F里还是保存着我们之前正常的FAP函数但在费盘数里他尝试递归的时候他在引用的费函数就已经是我们定义的G函数了也就是说在我们写地归函数的时候我们没有办法保证这个地规函数调用自己的时候调用的还是自己那到这里我们就要问了有没有可能从某种程度上避免这个问题那我们可以发现啊我们面临的问题很大程度上是我们老在全局变量这个范围里搞来搞去造成的那我们可以通过把地规这个操作封装起来来解决这个问题比如说我们可以把fib函数写成这样在费盘数内部定义一个下方线费盘数然后我们的费本返回这个下滑线费本我们看在这种情况下即便我们做了跟刚才相同的操作同时我们把下滑线Fib也复制成G它运行的结果依然是正确的因为在我们定义的这个Fib里面,它使用了一个臂包因此在下化线fib里面使用下化线fib的时候它只会在我们定义的这个Fib里面找而不会找到Global去通过这种封装我们就可以防止外面的代码来改变我们函数的运行当然大家可能觉得这种代码非常刻意首先 确实是因为我想用更短的代码来展示这个问题所以这里用了一个非常明显的不好的写法其次呢这个问题在我们的实际编程中确实也没有那么常见但如果我们回到之前的那个例子大家可以想象一下如果你遇到这样的代码你能反映过来为什么这两个print的结果不一样吗好 这期视频就到这里 希望大家有所帮助 喜欢的话记得点一下关注 别忘了一键三连哦优优独播剧场——YoYo Television Series Exclusive