Python对象可变性
Python中的变量保存对对象的引用。判断一次修改会影响谁,需要同时看对象是否允许原地改变,以及多个变量是否指向同一个对象。
定义
对象创建后允许原地改变内容的,称为可变对象;创建后内容不能原地改变,只能产生另一个对象并重新绑定的,称为不可变对象。
变量赋值b = a绑定的是对象引用。若a指向可变对象,b也指向同一对象,那么通过任一变量原地修改,两个变量都会观察到变化。若对象不可变,例如字符串,运算会产生新对象并让变量指向新对象,原对象保持不变。
性质
常见类型
| 类型 | 可变性 | 备注 |
|---|---|---|
int、float、bool、complex | 不可变 | 数值运算产生新对象 |
str | 不可变 | 字符串操作产生新字符串 |
tuple | 不可变 | 元组保存的引用不能替换,引用目标的内容仍可能改变 |
list | 可变 | 可以原地追加、删除或替换元素 |
dict | 可变 | 键必须是可哈希对象 |
set | 可变 | 元素必须是可哈希对象 |
frozenset | 不可变 | 不可变集合,可以作为字典的键 |
“不可变”描述对象自身能否原地改变,不表示对象引用的所有子对象都不可变。例如元组t = ([1],) 中的列表仍可执行t[0].append(2),因为这里修改的是列表;而t[0] = [2] 试图替换元组保存的引用,会抛出TypeError。
别名与复制
赋值b = a创建的是别名,不会复制对象。需要独立列表时,可以使用浅复制b = a[:] 或b = list(a)。浅复制会建立新的外层列表,但列表中的元素引用仍与原列表相同。
a = [1, 2]b = ab.append(3)print(a) # [1, 2, 3]重新绑定
不可变对象上的“修改”会重新绑定变量:
s = "ab"t = st += "c"print(s) # abprint(t) # abct += "c" 没有改变原来那个字符串,而是创建一个新字符串并让t指向它,因此s看到的仍是ab。
例子
修改共享列表
a = [1, 2]、b = a后,a is b为真。执行b.append(3) 后,两个变量都看到 [1, 2, 3],因为它们引用同一个列表对象。
切片建立独立外层列表
a = [1, 2]、b = a[:] 后,a is b为假。执行b.append(3) 只改变b,a仍是 [1, 2]。如果列表元素本身也是可变对象,浅复制后两个外层列表仍可能共享这些子对象。
元组内的可变元素
在t = ([1],) 中,t[0].append(2) 合法,结果中元组的第一个引用仍指向同一个列表;t[0] = [2] 不合法,因为它要替换元组保存的引用。
探索
切换共享引用、复制列表和字符串重新绑定,观察变量箭头指向的对象何时相同。
赋值之后,为什么有时两个变量会一起改变?
比较共享的可变对象、独立副本和不可变对象的重新绑定。
正在准备演示…
练习
关于可变性,下列说法正确的是:
参考解答
元组不可变只约束它保存的引用,元组里的列表内容仍可修改,B错;切片返回新列表,不是别名,D错。
设
a = [1, 2],执行b = a与b.append(3)后,a的值是:参考解答
b = a只复制引用,两者指向同一个列表;通过b原地追加,a也会看到新元素。设
s = "ab",执行t = s和t += "c"后,s的值是:参考解答
字符串不可变,
+=让t绑定到新字符串abc,原来的ab没有被修改。