对象与引用:身份、别名与可变性
核心概念
Python中一切皆对象,名字绑定到对象的引用。多个名字可以绑定到同一对象(别名),修改可变对象会影响所有别名。
关键概念:
- 身份(identity):对象的唯一标识,用
id()获取,is比较 - 相等性(equality):对象的值是否相同,用
==比较 - 别名(aliasing):多个名字指向同一对象
- 可变性(mutability):对象创建后能否修改
身份 vs 相等性
python
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True(值相等)
print(a is b) # False(不是同一对象)
print(a is c) # True(同一对象)
print(id(a) == id(c)) # True规则:
is比较身份(内存地址)==比较值(调用__eq__方法)- 身份相同 → 相等
- 相等 ≠ 身份相同
别名与可变对象
python
list1 = [1, 2, 3]
list2 = list1 # list2 是 list1 的别名
list2.append(4)
print(list1) # [1, 2, 3, 4],list1 也变了!问题:修改list2影响了list1,因为它们是同一对象。
不可变对象无此问题
python
a = "hello"
b = a
b = b + " world" # 创建新字符串
print(a) # "hello",a 不受影响字符串不可变,b + " world"创建新对象,b重新绑定,a仍指向原对象。
浅拷贝 vs 深拷贝
浅拷贝
创建新容器,但元素仍是原对象的引用:
python
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original) # 或 original.copy() 或 original[:]
shallow.append([5, 6])
print(original) # [[1, 2], [3, 4]],不受影响
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]],受影响!解释:
shallow是新列表,追加元素不影响originalshallow[0]和original[0]是同一个列表对象,修改它影响双方
深拷贝
递归复制所有嵌套对象:
python
deep = copy.deepcopy(original)
deep[0].append(999)
print(original) # [[1, 2, 99], [3, 4]],不受影响
print(deep) # [[1, 2, 99, 999], [3, 4]]深拷贝创建完全独立的对象树。
何时用哪种
- 浅拷贝:容器元素是不可变对象,或不需要独立修改元素
- 深拷贝:嵌套可变对象,需要完全独立的副本
- 赋值:只需要别名,不需要副本
函数参数是引用传递
python
def modify_list(lst):
lst.append(4)
numbers = [1, 2, 3]
modify_list(numbers)
print(numbers) # [1, 2, 3, 4]函数接收的是对象引用,修改可变参数会影响调用者。
如何避免
python
def safe_modify(lst):
lst = lst.copy() # 本地副本
lst.append(4)
return lst
numbers = [1, 2, 3]
result = safe_modify(numbers)
print(numbers) # [1, 2, 3],不受影响
print(result) # [1, 2, 3, 4]或在调用时传递副本:
python
modify_list(numbers.copy())默认参数陷阱再探
python
def add_to(element, target=[]):
target.append(element)
return target
print(add_to(1)) # [1]
print(add_to(2)) # [1, 2],不是 [2]!原因:默认值[]在函数定义时创建一次,所有调用共享。
查看默认值:
python
print(add_to.__defaults__) # ([1, 2],)修复:
python
def add_to(element, target=None):
if target is None:
target = []
target.append(element)
return target可变类型作为字典键
python
# 错误
d = {}
key = [1, 2]
d[key] = "value" # TypeError: unhashable type: 'list'列表、字典、集合是可变的,不能作为字典键或集合元素。
可哈希类型(可作为键):
- 不可变类型:int, float, str, tuple(元素也不可变), frozenset
- 用户定义类(默认可哈希,除非定义了
__eq__而未定义__hash__)
python
# 正确
d = {}
key = (1, 2) # 元组
d[key] = "value"
print(d[(1, 2)]) # "value"对象的生命周期
Python 使用引用计数和垃圾回收管理内存。对象无引用时被回收:
python
a = [1, 2, 3]
b = a
del a # 删除名字 a,但对象仍存在(b 引用它)
print(b) # [1, 2, 3]
del b # 现在对象无引用,可被回收循环引用
python
a = []
b = []
a.append(b)
b.append(a) # 循环引用Python 的循环垃圾回收器会处理循环引用。
练习
预测输出:
pythonx = [1, 2] y = [1, 2] z = x print(x == y, x is y, x is z)别名陷阱:修复以下代码,使
append_twice不影响原列表:pythondef append_twice(item, lst): lst.append(item) lst.append(item) numbers = [1, 2, 3] append_twice(4, numbers) print(numbers) # 期望 [1, 2, 3]浅拷贝问题:解释并修复:
pythonmatrix = [[0] * 3 for _ in range(3)] matrix[0][0] = 1 print(matrix) # 期望 [[1, 0, 0], [0, 0, 0], [0, 0, 0]] # 错误写法 bad_matrix = [[0] * 3] * 3 bad_matrix[0][0] = 1 print(bad_matrix) # 实际输出什么?深拷贝:编写函数
independent_copy(nested_list),返回完全独立的副本。可哈希类型:哪些可以作为字典键?
[1, 2],(1, 2),{1, 2},(1, [2, 3]),frozenset([1, 2])
完成练习后,打开本章答案页核对。
下一步
理解了对象引用与可变性,下一节深入列表与元组:切片、方法、列表推导式。
回到第三章概览 · 返回Python学习路线