Python & AI Tutorials Logo
Python 编程

21. 变量作用域与名称解析

当你在 Python 中创建一个变量时,它“存在”在哪里?函数能看到在它外部创建的变量吗?函数外部的代码能访问在函数内部创建的变量吗?这些问题都与作用域(scope)有关——也就是程序中某个名称可见并可以使用的区域。

理解作用域对于编写能正确且可预测运行的函数至关重要。缺少这方面的知识,你可能会不小心制造 bug:变量的值和你预期的不一致,或者对变量的修改并没有按预期持久保存。

在本章中,我们将探索 Python 如何确定某个名称引用的是哪个变量、如何控制变量在何处可访问,以及当你删除一个名称时会发生什么。到最后,你将理解支配 Python 程序中变量可见性的规则。

21.1) 局部变量与全局变量

Python 中的每个变量都存在于某个特定的作用域(scope)之内——这是一个变量名被定义并且可访问的代码区域。最基础的两种作用域是局部(local)全局(global)

理解全局作用域

在程序顶层——任何函数之外——创建的变量存在于全局作用域中。这些变量称为全局变量,在它们被定义之后,你的模块中任何位置都可以访问它们。

python
# 全局变量——在模块层级定义
total_users = 0
 
def show_user_count():
    # 这个函数可以读取全局变量
    print(f"Total users: {total_users}")
 
show_user_count()  # Output: Total users: 0
print(total_users)  # Output: 0

在这个例子中,total_users 是一个全局变量。函数 show_user_count() 和模块层级的代码都可以访问它。你可以把全局变量理解为在整个程序文件中都是可见的。

理解局部作用域

在函数内部创建的变量存在于该函数的局部作用域中。这些变量称为局部变量,并且只能在定义它们的函数内部访问。一旦函数执行结束,局部变量就会消失。

python
def calculate_discount(price):
    # discount_rate 是此函数的局部变量
    discount_rate = 0.15
    discount_amount = price * discount_rate
    return discount_amount
 
result = calculate_discount(100)
print(result)  # Output: 15.0
 
# 这会导致错误——这里不存在 discount_rate
# print(discount_rate)  # NameError: name 'discount_rate' is not defined

变量 discount_ratediscount_amount 只在 calculate_discount() 运行时存在。函数返回后,这些名称就不再存在了。其实这是一件好事——它能防止函数用临时变量把你的程序弄得很杂乱。

为什么局部作用域很重要

局部作用域提供了封装(encapsulation)——每个函数都有自己私有的工作空间。这意味着你可以在不同函数中使用相同的变量名而不会冲突:

python
def calculate_tax(amount):
    rate = 0.08  # 局部变量
    return amount * rate
 
def calculate_shipping(weight):
    rate = 5.00  # 名称相同但不同的局部变量
    return weight * rate
 
tax = calculate_tax(100)
shipping = calculate_shipping(3)
 
print(f"Tax: ${tax}")         # Output: Tax: $8.0
print(f"Shipping: ${shipping}")  # Output: Shipping: $15.0

两个函数都使用了名为 rate 的变量,但它们在不同的局部作用域中,是完全独立的变量。在一个函数里对 rate 的更改不会影响另一个函数里的 rate。这种隔离让函数更可靠,也更容易理解。

在函数中读取全局变量

函数可以读取全局变量而无需任何特殊语法:

python
# 全局配置
max_login_attempts = 3
 
def check_login(password):
    # 读取全局变量
    if password == "secret123":
        return "Login successful"
    else:
        return f"Invalid password. You have {max_login_attempts} attempts."
 
result = check_login("wrong")
print(result)  # Output: Invalid password. You have 3 attempts.

函数 check_login() 可以读取 max_login_attempts,因为它是全局变量。不过,这里有一个我们需要理解的重要限制。

赋值会创建局部变量的规则

作用域的棘手之处就在这里。如果你在函数内部对某个变量名进行赋值,Python 会创建一个同名的新的局部变量,即使已经存在同名的全局变量:

python
counter = 0  # 全局变量
 
def increment_counter():
    # WARNING: 这会创建一个名为 counter 的新局部变量——仅用于演示
    # PROBLEM: 在对局部 counter 赋值之前就尝试读取它
    counter = counter + 1  # UnboundLocalError: local variable 'counter' referenced before assignment
    print(counter)
 
# increment_counter() # This call results in UnboundLocalError

这段代码失败的原因是:Python 看到了赋值语句 counter = counter + 1,就判定 counter 必定是一个局部变量。可当它要计算 counter + 1 时,局部变量 counter 还没有值——我们试图在赋值前使用它。

这是一个常见的困惑来源。规则是:只要函数体中任何位置对某个变量名进行了赋值,该名称在整个函数中都会被当作局部变量,即使是在赋值语句之前。

我们用更直观的例子来看:

python
message = "Hello"  # 全局变量
 
def show_message():
    print(message)  # 这能工作——只是读取全局变量
    
def change_message():
    # WARNING: 这演示了一个常见错误——仅用于演示
    # PROBLEM: Python 看到下面的赋值,所以 message 在整个函数中都被当作局部变量
    print(message)  # UnboundLocalError!
    message = "Goodbye"  # 这会让 message 在整个函数中都变为局部变量
 
show_message()  # Output: Hello
# change_message()  # This call results in UnboundLocalError

函数 show_message() 可以正常工作,因为它只读取 message。但 change_message() 会失败,因为第二行的赋值让 Python 在整个函数中都把 message 当作局部变量,包括赋值前的 print() 语句。

参数是局部变量

函数参数是局部变量,它们的初始值来自函数调用时传入的参数:

python
def greet(name):  # 'name' 是一个局部变量
    greeting = f"Hello, {name}!"  # 'greeting' 也是局部变量
    return greeting
 
message = greet("Alice")
print(message)  # Output: Hello, Alice!
 
# 这里既不存在 'name' 也不存在 'greeting'
# print(name)  # NameError

参数 name 只在 greet() 函数内部存在。它在函数被调用时创建,在函数返回时消失。

实用示例:购物车计算

让我们看看在一个真实场景中,局部作用域与全局作用域是如何一起工作的:

python
# 全局配置
tax_rate = 0.08
free_shipping_threshold = 50
 
def calculate_total(subtotal):
    # 本次计算使用的局部变量
    tax = subtotal * tax_rate  # 读取全局 tax_rate
    
    # 确定运费
    if subtotal >= free_shipping_threshold:  # 读取全局阈值
        shipping = 0
    else:
        shipping = 5.99
    
    total = subtotal + tax + shipping
    return total
 
# 针对不同购物车金额计算
cart1 = calculate_total(30)
cart2 = calculate_total(60)
 
print(f"Cart 1 total: ${cart1:.2f}")  # Output: Cart 1 total: $38.39
print(f"Cart 2 total: ${cart2:.2f}")  # Output: Cart 2 total: $64.80

在这个例子中:

  • tax_ratefree_shipping_threshold 是全局配置值
  • subtotaltaxshippingtotal 对每一次 calculate_total() 调用来说都是局部变量
  • 每次函数调用都会得到一套彼此独立的局部变量
  • 函数可以读取全局配置,但不会修改它

这种关注点分离让代码更清晰:全局变量保存对所有地方都适用的配置,而局部变量保存仅对每次函数调用有效的临时计算结果。

21.2) 名称解析的 LEGB 规则

当 Python 遇到一个变量名时,它如何知道你指的是哪个变量?Python 遵循一种特定的查找顺序,称为 LEGB 规则(LEGB rule)。LEGB 代表 Local、Enclosing、Global、Built-in——Python 按照这个顺序搜索的四种作用域。

LEGB 中的四种作用域

让我们理解 LEGB 层级中的每一种作用域:

  1. Local (L):当前函数的作用域
  2. Enclosing (E):任意外围函数的作用域(包含当前函数的函数)
  3. Global (G):模块级作用域
  4. Built-in (B):Python 的内置名称,比如 printlenint

当你使用一个变量名时,Python 会按顺序搜索这些作用域:L → E → G → B。它使用找到的第一个匹配项,并停止继续搜索。

局部作用域:Python 首先查找的地方

Python 总是先检查局部作用域:

python
def calculate_price():
    price = 100  # 局部变量
    tax = 0.08   # 局部变量
    total = price * (1 + tax)
    return total
 
result = calculate_price()
print(result)  # Output: 108.0

当 Python 在 calculate_price() 内部看到 pricetaxtotal 时,它会在局部作用域中找到它们并使用这些值。查找在局部作用域就停止了——Python 不需要继续向外查找。

全局作用域:当局部作用域中没有时

如果名称在局部作用域中找不到,Python 会检查全局作用域:

python
# 全局变量
default_tax_rate = 0.08
default_currency = "USD"
 
def calculate_price(amount):
    # 'amount' 是局部变量,会立即找到
    # 'default_tax_rate' 不是局部变量,会在全局作用域中找到
    total = amount * (1 + default_tax_rate)
    return total
 
result = calculate_price(100)
print(result)  # Output: 108.0

当 Python 在函数内部遇到 default_tax_rate 时,它在局部作用域中找不到,就会去搜索全局作用域并在那里找到它。

内置作用域:Python 预定义的名称

如果名称在局部作用域或全局作用域中都找不到,Python 会检查内置作用域——也就是 Python 自动提供的名称:

python
def process_data(numbers):
    # 'numbers' 是局部变量
    # 'len' 既不是局部也不是全局——它是内置的
    count = len(numbers)
    
    # 'max' 也是内置的
    maximum = max(numbers)
    
    return count, maximum
 
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result)  # Output: (5, 30)

名称 lenmax 并不是在你的代码中定义的——它们是 Python 提供的内置函数。当 Python 在局部与全局都找不到这些名称时,它会去内置作用域中查找并在那里找到它们。

封闭作用域:嵌套函数

当你有嵌套函数(在其他函数内部定义的函数)时,封闭作用域就会发挥作用。这也是 LEGB 中 “E” 的重要性所在:

python
def outer_function():
    outer_var = "I'm from outer"  # 对 inner_function 来说属于封闭作用域
    
    def inner_function():
        inner_var = "I'm from inner"  # inner_function 的局部变量
        # inner_function 可以看到 inner_var(局部)和 outer_var(封闭)
        print(inner_var)   # Output: I'm from inner
        print(outer_var)   # Output: I'm from outer
    
    inner_function()
 
outer_function()

对于 inner_function() 来说,outer_function() 的作用域是一个封闭作用域。当 inner_function() 引用 outer_var 时,Python 会按顺序搜索:

  1. inner_function() 的局部作用域——未找到
  2. outer_function() 的封闭作用域——找到了!使用该值

LEGB 实战:简单示例

我们用一个清晰、直接的例子来看看四种作用域如何一起工作:

python
# 内置:len(由 Python 提供)
# 全局:multiplier
multiplier = 10
 
def outer(x):
    # inner 的封闭作用域
    y = 5
    
    def inner(z):
        # 局部作用域
        # z 是局部变量 (L)
        # y 来自封闭作用域 (E)
        # multiplier 来自全局作用域 (G)
        # len 来自内置作用域 (B)
        result = len([z, y, multiplier])  # 使用全部四种作用域!
        return z + y + multiplier
 
    return inner(3)
 
answer = outer(100)
print(answer)  # Output: 18

当 Python 在 inner() 内部计算 z + y + multiplier 时:

  1. L (Local):找到 z = 3
  2. E (Enclosing):在 outer() 中找到 y = 5
  3. G (Global):找到 multiplier = 10
  4. B (Built-in):找到 len 函数

这个例子清楚地展示了 Python 如何搜索这四种作用域来解析名称。

遮蔽:当内部作用域隐藏外部名称时

如果同一个名称在多个作用域中都存在,最内层的作用域会“获胜”——这称为遮蔽(shadowing)

python
value = "global"
 
def outer():
    value = "enclosing"
    
    def inner():
        value = "local"
        print(value)  # Which value?
    
    inner()
    print(value)  # Which value?
 
outer()
print(value)  # Which value?

Output:

local
enclosing
global

每个 print() 语句看到的 value 都不同,因为 Python 在找到第一个匹配项后就停止了:

  • inner() 内部:在局部找到 value → 打印 "local"
  • outer() 内部但在 inner() 外部:在 outer() 的作用域中找到 value → 打印 "enclosing"
  • 在模块层级:在全局中找到 value → 打印 "global"

可视化 LEGB 搜索顺序

名称引用

在 Local 中找到?

使用 Local 的值

在 Enclosing 中找到?

使用 Enclosing 的值

在 Global 中找到?

使用 Global 的值

在 Built-in 中找到?

使用 Built-in 的值

NameError

这个图展示了 Python 的搜索过程。它从最内层的作用域开始,逐步向外查找。如果名称在任何作用域中都找不到,Python 就会抛出 NameError

为什么 LEGB 对编写函数很重要

理解 LEGB 能帮助你:

  1. 预测变量值:你能确切知道 Python 会使用哪个变量
  2. 避免命名冲突:你理解名称何时会相互遮蔽
  3. 设计更好的函数:你可以决定每个变量应该放在何种作用域中
  4. 调试作用域问题:当变量值不符合预期时,你可以沿着 LEGB 追踪查找过程

LEGB 规则是 Python 解析名称方式的基础。每次你使用一个变量时,Python 都在幕后遵循这条规则。

21.3) 谨慎使用 global 关键字

我们已经看到函数可以读取全局变量,但如果你需要在函数内部修改全局变量呢?这就要用到 global 关键字——但应该谨慎且尽量少用。

问题:赋值会创建局部变量

如前所述,在函数内部对变量赋值会创建一个局部变量:

python
counter = 0  # 全局变量
 
def increment():
    # WARNING: 这会创建一个名为 counter 的新局部变量——仅用于演示
    # PROBLEM: 在对局部 counter 赋值之前就尝试读取它
    counter = counter + 1  # UnboundLocalError!
    
# increment()  # This call results in UnboundLocalError

这会失败,因为 Python 看到了赋值,就把 counter 当作整个函数中的局部变量。但我们试图在对它进行局部赋值之前读取 counter

这是使用全局变量时最常见的错误之一。错误信息 UnboundLocalError: local variable 'counter' referenced before assignment 精确地说明了发生了什么:Python 判定 counter 是局部变量(因为有赋值),但你在给它赋值之前就尝试使用它。

解决方案:将变量声明为全局

global 关键字会告诉 Python:“不要创建这个名字的新局部变量,而是使用全局变量。”

python
counter = 0  # 全局变量
 
def increment():
    global counter  # 告诉 Python 使用全局 counter
    counter = counter + 1  # 现在会修改全局变量
 
print(f"Before: {counter}")  # Output: Before: 0
increment()
print(f"After: {counter}")   # Output: After: 1
increment()
print(f"After again: {counter}")  # Output: After again: 2

global counter 声明必须在你使用该变量之前出现。它告诉 Python,在此函数中对 counter 的任何赋值都应该修改全局变量,而不是创建局部变量。

多个全局变量

你可以在一条语句中把多个变量声明为全局变量:

python
total_sales = 0
total_customers = 0
 
def record_sale(amount):
    global total_sales, total_customers
    total_sales += amount
    total_customers += 1
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $0, Customers: 0
 
record_sale(25.50)
record_sale(30.00)
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $55.5, Customers: 2

total_salestotal_customers 都被声明为全局变量,因此函数可以修改它们两者。

何时使用 global:共享状态

当你需要维护共享状态(shared state)时——也就是多个函数需要访问并修改的数据——使用 global 是合适的:

python
# 游戏状态
player_score = 0
player_lives = 3
game_over = False
 
def award_points(points):
    global player_score
    player_score += points
    print(f"Score: {player_score}")
 
def lose_life():
    global player_lives, game_over
    player_lives -= 1
    print(f"Lives remaining: {player_lives}")
    
    if player_lives <= 0:
        game_over = True
        print("Game Over!")
 
def check_game_status():
    # 只是读取全局变量——不需要 global 关键字
    if game_over:
        return "Game Over"
    else:
        return f"Playing - Score: {player_score}, Lives: {player_lives}"
 
# 进行游戏
award_points(100)    # Output: Score: 100
award_points(50)     # Output: Score: 150
lose_life()          # Output: Lives remaining: 2
print(check_game_status())  # Output: Playing - Score: 150, Lives: 2

这个例子展示了对 global 的恰当使用:多个函数需要修改共享的游戏状态。不过请注意,check_game_status() 不需要 global,因为它只是读取这些变量。

为什么应该谨慎使用 global

虽然 global 有时是必要的,但过度使用会让代码更难理解和维护。原因如下:

问题 1:隐藏的依赖关系

当函数修改全局变量时,从函数调用本身看不出哪些内容会发生变化:

python
total = 0
 
def add_to_total(value):
    global total
    total += value
 
# 这个函数做了什么?不读代码你看不出来
add_to_total(10)

与返回值的写法对比:

python
def add_to_total(current_total, value):
    return current_total + value
 
total = 0
total = add_to_total(total, 10)  # 清晰:total 正在被更新

第二个版本明确表示 total 正在被修改。

问题 2:测试变得更困难

修改全局状态的函数更难测试,因为你需要设置并重置全局变量:

python
# 难以测试——依赖全局状态
score = 0
 
def add_score(points):
    global score
    score += points
 
# 每个测试都需要重置 score
# Test 1
score = 0
add_score(10)
assert score == 10
 
# Test 2 - must reset score again
score = 0
add_score(20)
assert score == 20

问题 3:函数不可复用

依赖特定全局变量的函数无法轻易在其他程序中复用:

python
# 这个函数只有在存在名为 'inventory' 的全局变量时才工作
inventory = []
 
def add_item(item):
    global inventory
    inventory.append(item)

避免使用 global 的其他做法

很多情况下,你可以通过使用返回值与参数来避免 global

不要修改全局状态:

python
# 使用 global(不太理想)
balance = 1000
 
def withdraw(amount):
    global balance
    if amount <= balance:
        balance -= amount
        return True
    return False
 
withdraw(100)
print(balance)  # Output: 900

使用返回值:

python
# 使用返回值(更好)
def withdraw(balance, amount):
    if amount <= balance:
        return balance - amount, True
    return balance, False
 
balance = 1000
balance, success = withdraw(balance, 100)
print(balance)  # Output: 900

第二种写法更灵活、更易测试、也更容易复用。

何时 global 确实合适

global 确实有一些合理用途:

  1. 确实需要全局的配置:
python
# 全应用范围设置
debug_mode = False
log_level = "INFO"
 
def enable_debug():
    global debug_mode, log_level
    debug_mode = True
    log_level = "DEBUG"
  1. 用于调试或统计的计数器:
python
# 用于调试:追踪函数调用次数
_function_call_count = 0
 
def tracked_function():
    global _function_call_count
    _function_call_count += 1
    # ... rest of function

关于 global 的关键要点

  • 只有在确实需要修改模块级状态时才使用 global
  • 尽量优先使用返回值和参数
  • 当你确实使用 global 时,要说明为什么它是必要的
  • 想想你的设计是否可以改进以避免 global
  • 记住:读取全局变量不需要 global 关键字——只有修改时才需要

21.4) 使用 nonlocal 修改封闭函数中的变量

当你有嵌套函数时,你可能需要修改封闭函数作用域中的变量。nonlocal 关键字就是为此而生——它类似 global,但作用对象是封闭函数作用域,而不是全局作用域。

问题:修改封闭作用域中的变量

就像默认赋值会创建局部变量一样,封闭作用域也会出现同样的问题:

python
def outer():
    count = 0  # outer 作用域中的变量
    
    def inner():
        # WARNING: 这会创建一个名为 count 的新局部变量——仅用于演示
        # PROBLEM: 在对局部 count 赋值之前就尝试读取它
        count = count + 1  # UnboundLocalError!
        print(count)
    
    inner()
 
# outer()  # This call results in UnboundLocalError

Python 在 inner() 中看到对 count 的赋值,就把它当作局部变量。但我们试图在局部赋值之前读取它,从而导致错误。

解决方案:nonlocal 关键字

nonlocal 关键字告诉 Python:“这个变量不是局部的——去封闭函数的作用域中找并使用那个变量。”

python
def outer():
    count = 0  # outer 作用域中的变量
    
    def inner():
        nonlocal count  # 使用 outer 作用域中的 count
        count = count + 1
        print(f"Count in inner: {count}")
    
    print(f"Count before: {count}")  # Output: Count before: 0
    inner()                          # Output: Count in inner: 1
    print(f"Count after: {count}")   # Output: Count after: 1
 
outer()

现在 inner() 就可以修改 outer() 作用域中的 count 变量了。由于我们修改的是封闭作用域中的实际变量,这个修改在 inner() 返回后仍然会保留。

为什么 nonlocal 很有用:会记住状态的函数

nonlocal 关键字支持一种强大的模式:内部函数可以维护并修改其封闭作用域中的状态。我们会在第 23 章详细学习闭包与工厂函数,但现在你只需要理解:nonlocal 允许内部函数修改封闭作用域中的变量。

下面是一个简单示例,展示 nonlocal 如何工作:

python
def create_counter():
    count = 0  # 对 increment 来说,这是封闭作用域中的变量
    
    def increment():
        nonlocal count  # 修改封闭作用域中的 count
        count += 1
        return count
    
    return increment  # 返回内部函数
 
# 创建一个计数器
counter1 = create_counter()
 
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
# 再创建一个相互独立的计数器
counter2 = create_counter()
 
print(counter2())  # Output: 1
print(counter2())  # Output: 2

每次调用 create_counter() 都会创建一个新的 count 变量,以及一个新的 increment() 函数;increment() 可以通过 nonlocal 修改对应的那个 count

nonlocal vs global

理解它们的区别很重要:

python
x = "global"
 
def outer():
    x = "enclosing"
    
    def use_global():
        global x  # 引用的是全局 x
        print(f"use_global sees: {x}")  # Output: use_global sees: global
    
    def use_nonlocal():
        nonlocal x  # 引用的是 outer 的 x
        print(f"use_nonlocal sees: {x}")  # Output: use_nonlocal sees: enclosing
    
    use_global()
    use_nonlocal()
 
outer()
  • global 总是引用模块级作用域
  • nonlocal 引用最近的封闭函数作用域

何时不能使用 nonlocal

nonlocal 只适用于封闭函数作用域。你不能把它用于:

  1. 全局作用域(应使用 global):
python
x = "global"
 
def func():
    nonlocal x  # SyntaxError: no binding for nonlocal 'x' found
    x = "modified"
  1. 在任何封闭作用域中都不存在的变量:
python
def outer():
    def inner():
        nonlocal count  # SyntaxError: no binding for nonlocal 'count' found

关于 nonlocal 的关键要点

  • nonlocal 来修改封闭函数作用域中的变量
  • nonlocal 会在封闭函数作用域中搜索,而不是全局作用域
  • 读取封闭变量不需要 nonlocal——只有修改时才需要
  • nonlocal 支持创建具有私有状态的函数这一强大模式
  • 我们会在第 23 章学习更多关于闭包与工厂函数的内容

nonlocal 关键字对创建维护私有状态的函数尤其有用,正如在计数器等示例中所能看到的那样。

21.5) 使用 del 删除名称(而非对象)及其含义

有时你需要从程序的命名空间中移除一个变量——例如在长时间运行的程序中释放内存、清理临时变量,或者从集合中移除条目。Python 的 del 语句可以处理这些任务,但理解它究竟做了什么、以及没做什么很重要。

Python 中的 del 语句经常被误解。它不会删除对象——它删除的是名称(names)(变量绑定)。理解这种区别对于理解 Python 如何管理内存与引用至关重要。

del 实际做了什么

del 语句会从当前作用域中移除一个名称:

python
x = 42
print(x)  # Output: 42
 
del x
 
# print(x)  # NameError: name 'x' is not defined

执行 del x 后,名称 x 在当前作用域中就不再存在了。如果你尝试使用它,Python 会抛出 NameError,因为这个名称已经不再被定义。

删除名称 vs 删除对象

这就是关键洞察:del 移除的是名称,而不一定是该名称所引用的对象:

python
# 创建一个列表以及两个引用它的名称
original = [1, 2, 3]
reference = original  # 两个名称都引用同一个列表
 
print(original)   # Output: [1, 2, 3]
print(reference)  # Output: [1, 2, 3]
 
# 删除其中一个名称
del original
 
# 列表仍然存在,因为 'reference' 仍然引用它
print(reference)  # Output: [1, 2, 3]
 
# print(original)  # NameError: name 'original' is not defined

列表 [1, 2, 3] 仍然存在,因为 reference 仍然引用它。删除 original 仅仅移除了那个名称——并没有删除列表对象本身。

对象何时才会真正被删除

当某个对象不再被任何名称引用时,Python 会自动删除它。这称为垃圾回收(garbage collection)

python
data = [1, 2, 3]  # 创建列表,'data' 引用它
 
del data  # 删除名称 'data'
 
# 现在列表没有任何引用了,所以 Python 最终会删除它
# (这是自动发生的——你不需要做任何事)

当我们删除 data 时,列表 [1, 2, 3] 没有任何剩余引用,因此 Python 的垃圾回收器最终会回收其内存。但这是自动发生的——你无法控制具体何时发生。

从集合中删除条目

del 语句也可以从集合中移除条目,但这与删除名称在本质上不同。当你对集合的索引或切片使用 del 时,你是在修改集合本身,而不是删除一个名称。

这是一个重要区分:当你写 del numbers[2] 时,你是在对列表对象调用一个特殊方法以移除元素。名称 numbers 仍然存在,并且仍然引用同一个列表对象——只是列表现在元素更少了。

python
# 按索引删除列表元素
numbers = [10, 20, 30, 40, 50]
del numbers[2]  # 删除索引 2 处的元素
print(numbers)  # Output: [10, 20, 40, 50]
 
# 删除列表切片
numbers = [10, 20, 30, 40, 50]
del numbers[1:3]  # 删除索引 1 到 3(不含 3)的元素
print(numbers)  # Output: [10, 40, 50]
 
# 删除字典条目
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person)  # Output: {'name': 'Alice', 'city': 'Boston'}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai