大家好,我是正在实战各种 AI 项目的程序员晚枫。
你有没有遇到过这种情况:明明只改了一个变量,另一个变量也跟着变了? 这种 bug 特别隐蔽,我当年做项目的时候踩过无数次坑。今天咱们就把 Python 的引用机制彻底搞明白——搞懂了,这种 bug 再也难不住你。
🔗 引用与身份:变量不是盒子,是标签
一个场景引入
1 2 3 4 5 6 7 8 9
| a = [1, 2, 3] b = a b.append(4) print(a)
print(a is b) print(id(a) == id(b))
|
💡 核心概念:Python 中的变量不是"盒子"(存放值),而是"标签"(贴在对象上)。a = [1,2,3] 不是把列表放进 a 这个盒子,而是把 a 这个标签贴到了 [1,2,3] 这个对象上。
共享引用的几种情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| a = [1, 2, 3] b = a b.append(4) print(a)
def add_item(lst): lst.append("new") return lst
data = [1, 2, 3] result = add_item(data) print(data) print(result) print(data is result)
s1 = "hello" s2 = s1 s2 = "world" print(s1) print(s2)
|
值相等 vs 身份相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| a = [1, 2, 3] b = [1, 2, 3]
print(a == b) print(a is b)
x = 256 y = 256 print(x is y)
x = 257 y = 257 print(x is y)
s1 = "hello" s2 = "hello" print(s1 is s2)
|
⚠️ 面试必知:== 比较值,is 比较身份(内存地址)。判断 None 用 is None,不用 == None!
📋 深浅拷贝:复制也有大学问
浅拷贝:只复制第一层
1 2 3 4 5 6 7 8 9 10 11 12
| import copy
original = [[1, 2], [3, 4]] shallow = copy.copy(original)
shallow[0][0] = 'X' print(original)
shallow.append([5, 6]) print(original)
|
深拷贝:递归复制所有层
1 2 3 4 5 6 7 8
| import copy
original = [[1, 2], [3, 4]] deep = copy.deepcopy(original)
deep[0][0] = 'Y' print(original) print(deep)
|
浅拷贝的多种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import copy
data = [1, 2, 3, 4, 5]
c1 = copy.copy(data)
c2 = data[:]
c3 = list(data)
c4 = [x for x in data]
d = {'a': 1, 'b': 2} c5 = d.copy()
import timeit print(timeit.timeit('copy.copy(data)', globals=globals(), number=100000)) print(timeit.timeit('data[:]', globals=globals(), number=100000)) print(timeit.timeit('list(data)', globals=globals(), number=100000))
|
何时使用哪种拷贝
| 场景 | 推荐 | 原因 | 性能 |
|---|
| 简单扁平列表 | 切片 [:] | 最快 | ⭐⭐⭐⭐⭐ |
| 简单扁平列表 | list() | 快 | ⭐⭐⭐⭐ |
| 嵌套对象 | copy.deepcopy() | 安全 | ⭐⭐ |
| 只读访问 | 不拷贝 | 节省内存 | ⭐⭐⭐⭐⭐ |
| 字典浅拷贝 | .copy() | 惯用写法 | ⭐⭐⭐⭐ |
🔄 可变与不可变:一张表搞清楚
核心区别
| 类型 | 可变性 | 示例 | 修改时行为 |
|---|
| list | ✅ 可变 | [1,2,3] | 原地修改 |
| dict | ✅ 可变 | {'a':1} | 原地修改 |
| set | ✅ 可变 | {1,2,3} | 原地修改 |
| tuple | ❌ 不可变 | (1,2,3) | 创建新对象 |
| str | ❌ 不可变 | "hello" | 创建新对象 |
| int/float | ❌ 不可变 | 42 | 创建新对象 |
| frozenset | ❌ 不可变 | frozenset({1}) | 创建新对象 |
可变对象的陷阱
1 2 3 4 5 6 7 8 9 10 11 12 13
|
my_dict = {(1,2): 'value'}
my_set = {(1,2), (3,4)}
t = ([1, 2], [3, 4]) t[0].append(3) print(t)
|
⚠️ 可变默认参数:Python 最大坑之一
经典 Bug
1 2 3 4 5 6 7 8 9 10 11 12
| class BugExample: def __init__(self, items=[]): self.items = items
a = BugExample() a.items.append(1) b = BugExample() print(b.items)
print(a.items is b.items)
|
正确写法
1 2 3 4 5 6 7 8 9
| class GoodExample: def __init__(self, items=None): self.items = items if items is not None else []
a = GoodExample() a.items.append(1) b = GoodExample() print(b.items)
|
函数中的同类陷阱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def append_to(element, target=[]): target.append(element) return target
print(append_to(1)) print(append_to(2))
def append_to_fixed(element, target=None): if target is None: target = [] target.append(element) return target
print(append_to_fixed(1)) print(append_to_fixed(2))
|
🏗️ 实战案例:数据缓存系统的引用问题
场景:配置管理系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import copy
class ConfigManager: """配置管理器 —— 正确处理引用和拷贝""" def __init__(self, default_config): self._default = copy.deepcopy(default_config) self._configs = {} def get_config(self, name): """获取配置,返回深拷贝防止外部修改""" if name not in self._configs: return copy.deepcopy(self._default) return copy.deepcopy(self._configs[name]) def update_config(self, name, updates): """更新配置,基于当前配置的拷贝修改""" config = self.get_config(name) config.update(updates) self._configs[name] = config return config def reset_config(self, name): """重置为默认配置""" if name in self._configs: del self._configs[name]
default = {'debug': False, 'port': 8080, 'hosts': ['localhost']} mgr = ConfigManager(default)
external = mgr.get_config('app') external['hosts'].append('example.com') print(mgr.get_config('app')['hosts'])
|
性能对比:拷贝方式的选择
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import copy import timeit
simple_data = list(range(1000)) nested_data = {'users': [{'name': f'user_{i}', 'age': i} for i in range(100)]}
print("简单列表拷贝性能(10万次):") print(f" 切片: {timeit.timeit('simple_data[:]', globals=globals(), number=100000):.3f}s") print(f" list(): {timeit.timeit('list(simple_data)', globals=globals(), number=100000):.3f}s") print(f" copy.copy: {timeit.timeit('copy.copy(simple_data)', globals=globals(), number=100000):.3f}s") print(f" deepcopy: {timeit.timeit('copy.deepcopy(simple_data)', globals=globals(), number=100000):.3f}s")
print("嵌套字典拷贝性能(1万次):") print(f" copy.copy: {timeit.timeit('copy.copy(nested_data)', globals=globals(), number=10000):.3f}s") print(f" deepcopy: {timeit.timeit('copy.deepcopy(nested_data)', globals=globals(), number=10000):.3f}s")
|
🎯 本讲总结
引用与身份:is 比较身份(同一对象),== 比较值。变量是标签不是盒子。
深浅拷贝:copy() 只复制第一层,deepcopy() 递归复制所有层。简单对象用切片,嵌套对象用深拷贝。
可变默认参数:永远不要用可变对象(list/dict/set)作为函数默认参数,用 None 代替。
可变 vs 不可变:理解哪些类型是可变的,避免在字典键、集合元素、默认参数中使用可变对象。
📚 推荐教材
《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》
学习路线: 零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》
🎓 加入《流畅的 Python》直播共读营
学到这里,如果你想系统吃透这本书——欢迎加入我的直播共读课。
- 每周直播精讲,逐章拆解核心知识点
- 专属学习群,随时答疑交流
- 试运营特惠:
499 元 → 299 元
👉 【立即报名《流畅的 Python》共读课】:https://mp.weixin.qq.com/s/ivHJwn1nNx5ug4TFrapvGg
🔗 课程导航
← 上一讲:可调用对象 | 下一讲:序列和多态 →
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!
