Python的秘密生活:幻影副本

发布: (2026年2月6日 GMT+8 14:28)
5 分钟阅读
原文: Dev.to

Source: Dev.to

引言

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}")

输出

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}")

输出

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

相关文章

阅读更多 »

其他数据结构

元组 tuple - 用圆括号把多个值括起来表示 - 与列表相同但不可更改,不能添加、删除、修改 字典 dict - 键和值成对出现的数据结构 - 当键比顺序更重要时使用 - 用大括号 {} 表示 使用 python 的 .get 方法可以在键不存在时返回默认值。

100天中的第13天

所以今天我在学习使用不同的方法调试我的代码。虽然我没有完成最终项目,但我学会了几种调试技术,例如……

Python 运算和函数

表达式 Expression - 操作数和运算符的组合 操作数 Operand: 运算的对象 运算符 Operator: 表示某种运算的符号,例如:+, -, *, / 如果用编程语言编写以精确表达式表示的运算,计算机就会精确计算。 运算优先级 - 小括号 () …