基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Python中的命名空间和作用域

知识点图片

在 Python 中,命名空间(Namespace)作用域(Scope)是两个紧密相关但概念不同的核心机制。理解它们对于编写无 Bug 的代码、理解变量的生命周期以及解决变量名冲突至关重要。

简单来说:

  • 命名空间 决定了变量名和对象之间的绑定关系(变量存在哪里)。
  • 作用域 决定了在代码的哪个位置可以访问到这些变量(在哪里能找到变量)。

一、命名空间 (Namespace)

命名空间本质上是一个从名称到对象的映射。在 Python 内部,命名空间通常是用字典(Dictionary)来实现的。

1. 三种主要的命名空间

Python 中主要有三种命名空间,它们的生命周期各不相同:

  1. 内置命名空间 (Built-in Namespace)

    • 内容:包含 Python 内置的函数和异常(如 print(), len(), Exception 等)。
    • 生命周期:Python 解释器启动时创建,退出时销毁。
    • 查看方式dir(__builtins__)
  2. 全局命名空间 (Global Namespace)

    • 内容:包含模块(Module)级别的变量、函数定义、类定义等。
    • 生命周期:在模块被读取(import 或运行)时创建,解释器退出或模块被卸载时销毁。
    • 查看方式globals()
  3. 局部命名空间 (Local Namespace)

    • 内容:包含函数内部定义的变量、参数等。
    • 生命周期函数被调用时创建,函数返回或抛出异常时销毁。每次调用函数都会创建一个新的局部命名空间。
    • 查看方式locals()

2. 命名空间的查找顺序

虽然有不同的命名空间,但它们是隔离的。例如,两个不同的函数中都可以有一个名为 x 的变量,它们互不干扰,因为它们属于不同的局部命名空间。


二、作用域 (Scope) 与 LEGB 规则

作用域是指在代码的特定区域,你可以直接通过名称访问变量的范围。

Python 使用 LEGB 规则 来决定变量的查找顺序。当你引用一个变量时,Python 会按照以下顺序搜索命名空间:

  1. L (Local) - 局部作用域
    • 包含在当前函数或类方法内部定义的变量。
  2. E (Enclosing) - 嵌套(闭包)作用域
    • 包含在外部嵌套函数中的变量(例如:函数 A 里面定义了函数 B,B 访问 A 的变量)。
  3. G (Global) - 全局作用域
    • 当前模块(脚本)最外层定义的变量。
  4. B (Built-in) - 内置作用域
    • Python 预定义的变量(如 open, range)。

查找原则:一旦找到第一个匹配的名称,搜索就会停止。如果四个层级都找不到,就会抛出 NameError

代码示例:LEGB 演示

python
x = "Global x"  # Global

def outer():
    x = "Enclosing x"  # Enclosing
    
    def inner():
        x = "Local x"  # Local
        print(x)
    
    inner()

outer()
# 输出: Local x
# 如果注释掉 inner 中的 x,输出: Enclosing x
# 如果再注释掉 outer 中的 x,输出: Global x

三、修改变量:globalnonlocal 关键字

默认情况下,在函数内部赋值一个变量,Python 会将其视为在该函数的局部作用域中创建一个新变量,而不是修改外部变量。如果你想修改外部作用域的变量,需要使用关键字。

1. global 关键字

用于在局部作用域中修改全局变量

python
count = 0  # 全局变量

def add():
    global count  # 声明我们要使用的是全局变量 count
    count += 1    # 修改它

add()
print(count)  # 输出: 1

如果不加 globalcount += 1 会报错(UnboundLocalError),因为 Python 认为你要修改一个未初始化的局部变量。

2. nonlocal 关键字

用于在嵌套函数(内部函数)中修改嵌套作用域(Enclosing scope)的变量。它不能用于全局变量。

python
def outer():
    x = 10
    
    def inner():
        nonlocal x  # 声明我们要使用的是 outer 函数中的 x
        x = 20
        
    inner()
    print(x)

outer()  # 输出: 20

如果不加 nonlocalinner 中的 x = 20 只会创建一个新的局部变量,outer 中的 x 仍然是 10。


四、常见陷阱 (The "Gotcha")

UnboundLocalError

这是一个非常经典的错误。请看下面的代码:

python
x = 10

def func():
    print(x)  # 试图打印全局的 x
    x = 20    # 试图定义局部的 x

func()

结果UnboundLocalError: local variable 'x' referenced before assignment

原因
Python 在编译函数时,发现函数内部有赋值语句 x = 20。因此,它将 x 标记为局部变量
当执行到 print(x) 时,Python 试图访问局部的 x,但此时赋值语句还没执行,局部变量 x 还没有绑定值,所以报错。

解决方法

  1. 如果只是想读取全局 x,不要在函数内对 x 赋值。
  2. 如果想修改全局 x,在函数开头加上 global x

五、总结

概念 描述 关键点
命名空间 名字到对象的映射(字典)。 内置、全局、局部。
作用域 变量可见的代码区域。 决定了你在哪里能访问变量。
LEGB 规则 变量查找顺序。 Local -> Enclosing -> Global -> Built-in
global 关键字。 允许在局部修改全局变量。
nonlocal 关键字。 允许在内部函数修改外部函数的变量。

理解这些规则,你就能明白为什么有时候变量“变了”,有时候又“没变”,以及为什么有时候会报“变量未定义”的错误。

00:00
00:00