Python 变量作用域详解(Python 3.x)

变量作用域是指变量可被访问的代码范围。与 Java 等语言不同,Python 的变量作用域规则更灵活,尤其在全局变量和局部变量的区分上有特殊机制。本文将详细解析 Python 中变量的作用域类型、访问规则及修改全局变量的方法。

作用域的基本类型

Python 中变量的作用域分为以下四种,按访问优先级从高到低排列:

  1. 局部作用域(Local):在函数内部定义的变量,仅在函数内有效。
  2. 嵌套作用域(Enclosing):在嵌套函数外层的函数中定义的变量(适用于闭包场景)。
  3. 全局作用域(Global):在模块(.py 文件)顶层定义的变量,整个模块内可访问。
  4. 内置作用域(Built-in):Python 内置的变量或函数(如 printlen)。

查找顺序:L → E → G → B(由内向外)

局部变量(Local)

在函数内部定义的变量默认为局部变量,仅在函数执行期间存在,函数外部无法访问。

示例:局部变量的特性
# 全局变量 x
x = 50

def function_local(x_param):
    # 函数内部的 x 是局部变量(参数也是局部变量)
    print(f"函数内初始 x(参数):{x_param}")
    x_local = 2  # 局部变量
    print(f"函数内修改后局部 x:{x_local}")

# 调用函数
function_local(x)

# 函数外部无法访问局部变量
print(f"函数外全局 x:{x}")  # 输出:50(不受函数内局部变量影响)
# print(x_local)  # 报错:NameError: name 'x_local' is not defined

输出结果

函数内初始 x(参数):50
函数内修改后局部 x:2
函数外全局 x:50

结论

  • 函数参数(如 x_param)也是局部变量。
  • 函数内部定义的变量(如 x_local)仅在函数内有效。
  • 局部变量与全局变量同名时,函数内优先使用局部变量。

全局变量(Global)

在模块顶层定义的变量为全局变量,可在模块内的任何函数外访问。若要在函数内修改全局变量,需用 global 关键字声明。

访问全局变量(无需声明)

函数内可直接访问全局变量(无需 global 声明):

# 全局变量
greeting = "Hello"

def print_greeting():
    # 直接访问全局变量
    print(greeting)

print_greeting()  # 输出:Hello
修改全局变量(需用 global 声明)

若在函数内直接修改全局变量,Python 会将其视为局部变量(创建新的局部变量),而非修改全局变量。如需修改,必须用 global 声明:

# 全局变量
count = 0

def increment():
    # 声明使用全局变量 count(而非创建局部变量)
    global count
    count += 1  # 修改全局变量
    print(f"函数内 count:{count}")

increment()  # 输出:函数内 count:1
print(f"函数外 count:{count}")  # 输出:函数外 count:1(全局变量已被修改)

如果不声明 global

count = 0

def increment_error():
    count += 1  # 报错:UnboundLocalError(Python 认为 count 是局部变量,但未初始化)

increment_error()
global 关键字的作用
  • 告诉 Python:“该变量是全局变量,不要创建局部变量”。
  • global 声明必须放在变量使用之前。
name = "Alice"

def change_name():
    global name  # 声明在使用前
    name = "Bob"  # 修改全局变量

change_name()
print(name)  # 输出:Bob

嵌套作用域(Enclosing)

当函数嵌套时,内层函数可访问外层函数的变量(非全局),这种作用域称为嵌套作用域。若要修改外层函数的变量,需用 nonlocal 关键字(Python 3 新增)。

示例:嵌套函数与 nonlocal
def outer():
    # 外层函数的变量(嵌套作用域)
    message = "Hello from outer"

    def inner():
        # 声明使用外层函数的 message(非局部,非全局)
        nonlocal message
        message = "Hello from inner"  # 修改外层变量
        print(f"内层函数:{message}")

    inner()
    print(f"外层函数:{message}")  # 输出修改后的值

outer()

输出结果

内层函数:Hello from inner
外层函数:Hello from inner

说明

  • 若不用 nonlocal,内层函数的 message 会被视为局部变量。
  • nonlocal 仅用于嵌套作用域,不可用于全局变量。
闭包(Closure)

当内层函数返回并被外部使用时,外层函数的变量会被“记住”,这就是闭包。

def make_multiplier(n):
    """创建一个乘以 n 的函数"""
    def multiplier(x):
        return x * n        # n 来自外层函数
    return multiplier

# 创建两个不同的闭包
double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))    # 10
print(triple(5))    # 15
print(triple(10))   # 30

# 查看闭包捕获的变量
print(double.__closure__[0].cell_contents)  # 2

内置作用域(Built-in)

Python 内置的名称

Python 预定义了一些内置函数和异常,它们位于 builtins 模块中。

# 常用内置函数
print(len([1, 2, 3]))       # 3
print(max(10, 20, 30))      # 30
print(type(42))             # <class 'int'>

# 查看所有内置名称
# import builtins
# print(dir(builtins))
内置作用域被遮蔽的问题

如果不小心定义了与内置函数同名的变量,会“遮蔽”内置函数。

# ⚠️ 严重问题:遮蔽了内置的 len 函数
len = 100                   # 创建了全局变量 len

def get_length(items):
    return len(items)       # ❌ TypeError: 'int' object is not callable

# 恢复:删除自己定义的 len
del len
print(len([1, 2, 3]))       # 3(恢复)

最佳实践:避免使用内置名称作为变量名(如 lenlistdictstrtype 等)

作用域查找规则:LEGB 原则

当访问一个变量时,Python 按以下顺序查找:

  1. L(Local):当前函数内部的局部变量。
  2. E(Enclosing):外层函数的变量(嵌套作用域)。
  3. G(Global):模块顶层的全局变量。
  4. B(Built-in):Python 内置的变量或函数。

若找不到变量,会抛出 NameError

示例:LEGB 原则演示
# 全局变量(G)
x = "global"

def outer():
    # 嵌套作用域变量(E)
    x = "outer"

    def inner():
        # 局部变量(L)
        x = "inner"
        print(x)  # 优先使用局部变量(L)→ 输出:inner

    inner()
    print(x)  # 外层变量(E)→ 输出:outer

outer()
print(x)  # 全局变量(G)→ 输出:global

# 访问内置函数(B)
print(len([1, 2, 3]))  # 调用内置的 len 函数 → 输出:3

常见问题与最佳实践

  1. 避免滥用全局变量

    • 全局变量会增加函数间的耦合,降低代码可读性。
    • 优先通过函数参数传递数据,而非依赖全局变量。
  2. 区分 globalnonlocal

    • global:用于修改全局变量(模块顶层)。
    • nonlocal:用于修改嵌套作用域的变量(外层函数)。
  3. 局部变量遮蔽(Shadowing)

    • 若局部变量与全局变量同名,局部变量会 “遮蔽” 全局变量(函数内优先使用局部)。
    x = 100
    def shadow():
        x = 200  # 局部变量,遮蔽全局 x
        print(x)  # 输出:200
    shadow()
    print(x)  # 输出:100(全局 x 未变)
    

更多推荐