局部作用域与全局作用域

我们来把“作用域”(Scope)这个编程中的核心概念拆解得明明白白。

### 核心比喻:全局村庄与函数作坊

想象你的整个 Python 程序是一个**“全局村庄”**。

  * **全局作用域 (Global Scope)**: 就是村庄的**公共广场**。在广场上放置的任何东西(全局变量),比如一个公告板 `message = "Hello Village!"`,是所有村民都能看见和读取的。这个广场在村庄建立(程序开始)时就存在,直到村庄废弃(程序结束)时才消失。

  * **局部作用域 (Local Scope)**: 当你调用一个函数时,就好比你临时搭建了一个**私密的“函数作坊”**。

      * 这个作坊是完全封闭的。你在里面创建的任何工具或材料(局部变量)都只属于这个作坊。

      * 当你的工作完成(函数返回),这个作坊会被**立即拆除**,里面所有的东西都会随之消失。下次再建同一个作坊,里面也是空空如也,不会记得上次留下的东西。

基于这个比喻,我们来逐一分析每个规则。

-----

### 1 局部变量不能在全局作用域内使用

**核心规则:** 作坊里的私有工具,在广场上是看不见也用不了的。

**原文代码分析:**

```python

def spam():

    # ❶ 'eggs' 在 spam 作坊内部被创建

    eggs = 31337

# 我们现在在村庄广场上

spam() # 1. 搭建 spam 作坊,创建 eggs=31337,然后工作完成,拆除作坊。

print(eggs) # 2. 在广场上大喊 "eggs 在哪?",但它已经随着作坊的拆除而消失了。

```

**详细解释:**

1.  程序执行到 `spam()` 时,Python 搭建了一个临时的 `spam` 作坊。

2.  在作坊内部,创建了一个名为 `eggs` 的工具,并给它贴上标签 `31337`。

3.  `spam` 函数执行完毕并返回,Python 立即将整个 `spam` 作坊连同里面的 `eggs` 工具一起彻底拆除销毁。

4.  程序回到村庄广场(全局作用域),执行 `print(eggs)`。Python 在广场上寻找名为 `eggs` 的东西,但一无所获。因此,它只能报错:`NameError: name 'eggs' is not defined`(“找不到叫'eggs'的东西”)。

-----

### 2 局部作用域不能使用其他局部作用域内的变量

**核心规则:** 你在自己的作坊里,无法使用隔壁另一个作坊里的私有工具。

**原文代码分析:**

```python

def spam():

    # ❶ spam 作坊的私有变量

    eggs = 99

    # ❷ 从 spam 作坊内部,要求搭建一个 bacon 作坊

    bacon()

    # ❸ bacon 作坊拆除后,回到 spam 作坊,打印自己作坊里的 eggs

    print(eggs)

def bacon():

    ham = 101

    # ❹ bacon 作坊自己的私有变量,和 spam 里的那个无关

    eggs = 0

# ❺ 在村庄广场上,要求搭建 spam 作坊

spam()

```

**执行流程的“作坊”视角:**

1.  **`spam()` 被调用**: 搭建 `spam` 作坊。在作坊内,创建工具 `eggs = 99`。

2.  **`bacon()` 被调用**: `spam` 的工作暂停。在 `spam` 作坊旁边,又搭建了一个全新的、独立的 `bacon` 作坊。

3.  **进入 `bacon` 作坊**: 在这个新环境里,创建了 `ham = 101` 和 `eggs = 0`。这个 `eggs` 是 `bacon` 作坊的私有财产,和 `spam` 作坊里那个 `eggs` 毫无关系,它们只是恰好同名。

4.  **`bacon()` 返回**: `bacon` 的工作完成,它的作坊被**立即拆除**。里面的 `ham` 和 `eggs=0` 全部消失。

5.  **回到 `spam` 作坊**: 程序回到 `spam` 作坊被暂停的地方,继续执行 `print(eggs)`。此时,它会寻找\*\*当前所处作坊(`spam` 作坊)\*\*里的 `eggs`,找到的是 `99`。

6.  **输出**: 屏幕上打印出 `99`。

7.  **`spam()` 返回**: `spam` 工作完成,`spam` 作坊也被拆除。程序结束。

-----

### 3 全局变量可以在局部作用域中读取

**核心规则:** 在作坊里工作时,你可以随时探出头去看广场上的公共公告板。

**原文代码分析:**

```python

def spam():

    # 在 spam 作坊里,需要用到 eggs

    # Python: “先在 spam 作坊里找找有没有叫 eggs 的工具... 没有。”

    # Python: “那我去村庄广场上看看有没有叫 eggs 的公告板... 啊哈,找到了!”

    print(eggs)

# 在村庄广场上,立起一个公告板

eggs = 42

spam() # 搭建 spam 作坊

print(eggs)

```

**详细解释:**

这就是 Python 的变量查找规则,称为 **LEGB 规则**(Local -\> Enclosing -\> Global -\> Built-in)的简化版:

1.  当 `spam()` 函数中的 `print(eggs)` 执行时,Python 首先在**当前局部作用域**(`spam` 作坊)里寻找 `eggs`。

2.  它没有找到。于是,它会去**全局作用域**(村庄广场)寻找。

3.  在全局作用域中,它找到了 `eggs = 42`。于是,它读取这个值并打印出来。

4.  所以,第一个输出是 `42`。

5.  `spam()` 返回后,`print(eggs)` 在全局作用域执行,自然也打印出 `42`。

-----

### 4 名称相同的局部变量和全局变量

**核心规则:** 如果你在作坊里创建了一个和广场公告板同名的私有工具,那么在作坊里你会优先使用自己的私有工具,它会\*\*“遮蔽”(Shadow)\*\*掉广场上的那个。

**原文代码分析:**

```python

def spam():

    # ❶ spam 作坊的私有 'eggs',它遮蔽了全局的 'eggs'

    eggs = 'spam local'

    print(eggs)

def bacon():

    # ❷ bacon 作坊的私有 'eggs'

    eggs = 'bacon local'

    print(eggs) # 打印 bacon 作坊自己的 eggs

    spam()      # 搭建并进入 spam 作坊

    print(eggs) # 从 spam 返回后,打印 bacon 作坊自己的 eggs

# ❸ 村庄广场上的公告板 'eggs'

eggs = 'global'

bacon() # 开始搭建 bacon 作坊

print(eggs) # bacon 作坊拆除后,打印广场上的 eggs

```

**输出剖析:**

1.  `bacon local`: `eggs = 'global'` 被设置。`bacon()` 被调用,在 `bacon` 作坊内,`eggs = 'bacon local'` 被创建。`print(eggs)` 打印的是这个局部的 `'bacon local'`。

2.  `spam local`: 在 `bacon` 内部,`spam()` 被调用。在 `spam` 作坊内,又创建了一个更局部的 `eggs = 'spam local'`。`print(eggs)` 打印的是这个 `'spam local'`。`spam` 返回,这个变量消失。

3.  `bacon local`: 执行流回到 `bacon` 作坊。再次 `print(eggs)`,打印的依然是 `bacon` 作坊自己的 `'bacon local'`。`bacon` 返回,它的 `eggs` 变量消失。

4.  `global`: 执行流回到村庄广场。`print(eggs)` 打印的是广场上的全局变量 `'global'`。

### 一个重要的补充:`global` 关键字

你提供的文本没有提到,但至关重要的一点是:**如果你想在函数作坊内部,修改村庄广场上的全局公告板,该怎么办?**

默认情况下,如果你在函数内对一个变量进行赋值 (`=`),Python 会无条件地认为你正在创建一个**新的局部变量**。

**错误的例子(无法修改全局变量):**

```python

counter = 0

def increment():

    # Python 认为这是一个新的、局部的 counter,和全局的那个无关

    counter = counter + 1

    print(f"Inside function: {counter}")

increment()

print(f"Outside function: {counter}")

# 输出会报错!因为在 `counter = ...` 这行,Python 尝试读取右边的 `counter`

# 来计算,但此时它已经决定左边的 `counter` 是个新的局部变量,

# 所以右边的局部 `counter` 还没有值。

# UnboundLocalError: local variable 'counter' referenced before assignment

```

**正确的做法:使用 `global` 关键字**

`global` 关键字就像一个声明:“**我接下来要操作的这个变量,不是我作坊里的私有工具,而是村庄广场上的那个公共设施!**”

```python

counter = 0

def increment():

    # 郑重声明:我操作的是全局的 counter

    global counter

    counter = counter + 1

    print(f"Inside function: {counter}")

increment()

print(f"Outside function: {counter}")

```

**输出:**

```

Inside function: 1

Outside function: 1

```

这次,函数成功地修改了全局变量的值。

### 总结

  * **隔离性**: 作用域的主要目的是**保护**。它将函数封装成独立的单元,防止它们意外地修改程序其他部分的状态,这使得代码更易于维护和调试。

  * **查找顺序**: Python 会优先在最内层的局部作用域查找变量,如果找不到,再逐层向外(全局)寻找。

  * **遮蔽**: 内部作用域的同名变量会“隐藏”外部作用域的变量。

  * **修改全局**: 必须使用 `global` 关键字才能在函数内部修改全局变量。在大型程序中,应谨慎使用此功能。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容