Python的秘密生活:幻影副本

发布: (2026年2月6日 GMT+8 14:28)
5 min read
原文: Dev.to

Source: Dev.to

为什么在 Python 中 = 并不会真正复制你的数据。

Timothy 盯着屏幕,脸色苍白。“Margaret?我好像不小心删掉了一半的数据库。”

Margaret 立刻把椅子转过去,语气平静。“别慌。把发生的事情说清楚。”

“我在测试一个清理用户列表的脚本,” Timothy 解释道。“我想安全地测试它,所以先复制了一份列表。我以为如果我把复制的列表弄坏,原始的列表还能安全。”

他把代码给她看:

# Timothy's Safety Plan

# The original list of critical users
users = ["Alice", "Bob", "Charlie", "Dave"]

# Create a "backup" copy to test on
test_group = users

# Timothy deletes 'Alice' from the test group
test_group.remove("Alice")

# Check the results
print(f"Test Group: {test_group}")
print(f"Original Users: {users}")

Output

Test Group: ['Bob', 'Charlie', 'Dave']
Original Users: ['Bob', 'Charlie', 'Dave']

Timothy 垂头丧气地说:“看见了吗?我从 test_group 删除了 Alice,但她也从 users 列表里消失了!这怎么可能?我操作的是备份,而不是原始列表!”

地址,而不是房子

Margaret 研究了一下代码。“这是 Python 中最常见的误解之一。关键在于 Python 如何处理内存。”

她问:“当你写 test_group = users 时,你以为这条指令做了什么?”

Timothy 回答:“我以为它创建了一个新列表。我以为它把 users 中的所有名字复制到一个叫 test_group 的新变量里。”

“Python 为了效率会走捷径,” Margaret 解释道。“复制数据会消耗时间和内存,所以 Python 并不是复制房子,而是只复制地址。”

她画了一个简单的示意图:变量 users 指向一个装有名字列表的盒子。当你执行 test_group = users 时,实际上是把第二个变量指向同一个地址;两个名字指向完全相同的对象。

“所以 userstest_group 只是同一个对象的两个不同名字?”
“没错,” Margaret 微笑道。“这就像通过链接共享文档——任何人通过该链接看到的都是同一个文档,所做的修改对所有人都可见。”

打破链接

Timothy 问:“那我要怎么真正复制一份呢?我想要一个独立的盒子。”

“我们必须显式地说明,” Margaret 说。“告诉 Python 把数据取出来,构建一个新列表。”

她给他演示了 .copy() 方法:

# Margaret's Fix: Explicit Copying

users = ["Alice", "Bob", "Charlie", "Dave"]

# .copy() creates a brand new list with the same data
test_group = users.copy()

test_group.remove("Alice")

print(f"Test Group: {test_group}")
print(f"Original Users: {users}")

Output

Test Group: ['Bob', 'Charlie', 'Dave']
Original Users: ['Alice', 'Bob', 'Charlie', 'Dave']

Timothy 长舒了一口气。“Alice 安全了。”

Margaret 还补充了一个警告:.copy() 只会产生浅拷贝。如果列表中包含其他可变对象(例如内部列表),这些内部对象仍然是共享的。对于仅包含名字的简单列表,浅拷贝已经足够。

Margaret 的速查表

  • 陷阱: 认为 new_list = old_list 会创建副本。
  • 现实: 赋值(=)创建的是引用(一个昵称),而不是副本。两个变量指向同一个对象。
  • 原因: Python 这样做是为了节省内存并提升速度。

解决方案

  • 浅拷贝: new_list = old_list.copy()(推荐方式)。
  • 切片: new_list = old_list[:](较早的做法,但仍然常用)。

检查方法

你可以验证两个变量是否引用同一个对象:

id(a) == id(b)   # 如果 a 和 b 是同一个对象则返回 True

Timothy 在编辑器里记下笔记:“我再也不会把 = 当作‘复制’来使用了。”

“这是一种成长的仪式,” Margaret 向他保证。“每个 Python 开发者都会以艰难的方式学到这课。总好过在生产数据库上犯错,而是在测试脚本里先学会。”

Back to Blog

相关文章

阅读更多 »

Python的秘密生活:隐藏的返回

为什么你的 function 结果是 None —— 以及如何修复它。Timothy 看着屏幕笑容满面。他刚刚完成了对 pricing script 的 refactoring,使其变得整洁,……