2.6 可变对象和不可变对象

Python的数据类型分为可变类型和不可变类型,可变类型的对象的值是可以被修改的,称为可变对象,而不可变类型的对象的值是不可修改的,称为不可变对象。数值类型、str类型、tuple类型都是不可变类型,而list类型、set类型、dict类型等都是可变类型。例如,一个list对象的值是可以修改的,即可以对其中的元素进行修改、删除、添加等操作。例如:

a=[1,2,3]
a[0]=4
print(a)

输出:

[4, 2, 3]

可以看到,变量a引用的list对象的第1个元素被修改了。因为tuple对象是不可修改的,下面代码将产生错误:

t=(1,2,3)
t[0]=4

产生了类型错误“TypeError: 'tuple' object does not support item assignment”。

但下面代码不会有任何错误:

t=(1,2,3)
print(t)
t=(4,5,6)
print(t)

输出:

(1, 2, 3)
(4, 5, 6)

上述代码中的变量名分别指向了不同的tuple对象,而它们指向的tuple对象本身并没有被修改。

前面讲过,变量是对象的引用,也就是说,变量仅是对象的别名,而不是对象本身。当变量t引用不可修改的tuple对象(1,2,3)时,这个tuple对象不可修改,但可以对变量名t重新赋值,如“t=(4,5,6)”使变量t又引用了这个新的tuple对象。

可以通过下面代码进一步理解上述内容:

x="hello"
y=x
print(x is y)        #x, y指向同一个不可修改对象hello
print(id(x), id(y))
x="world"          #x引用了新对象world,而并未修改对象hello
print(y)             #y仍然引用的是对象hello
print(x)             #x引用的是新对象world
print(id(x), id(y))

输出:

True
17939718919841793971891984
hello
world
17939718914801793971891984

可以看到,变量x前后指向的是不同的对象,而并未修改变量x指向的对象。

同样,执行复合赋值语句,如“x+=1”等,并不修改x指向的对象,而是让x指向新创建的对象。例如:

x=2
y=x        #y和x指向了同1个对象2
print(id(x), "\t", id(y))
x+=1         #x+1等价于x=x+1。因为x指向的int对象是不可修改的(immutable)
              #用x+1的结果给x赋值,就是让x变量名指向这个新的结果值对象
print(x)
print(y)
print(id(x), "\t", id(y))

其中,“x+=1”使得x引用了新对象,而并没有修改原来x引用的不可变对象“2”。

输出:

1717202432    1717202432
3
2
1717202464    1717202432

如果1个变量指向的是1个可修改的(mutable)对象,那么可以通过这个变量修改它指向的对象,而这个时候并不会创建新对象。例如:

a=[2,3]
b=a        #b和a都是list对象[2,3]的引用
a[0]=4     #修改a指向的list对象的第1个元素
print(a)
print(b)
print(id(a))
print(id(b))
b[1]=7
print(a)
print(b)

输出:

[4, 3]
[4, 3]
2214058333320
2214058333320
[4, 7]
[4, 7]

可以看出,上述代码修改a、b指向的list对象的元素,并没有修改a、b本身。因此,a、b始终指向的是同1个对象,无论变量指向的是1个可修改的对象还是不可修改的对象,给这个变量赋值都会使这个变量引用其他的对象。例如:

a=[2,3]
print(id(a))
a=[4,5]
print(id(a))

输出:

1810380911112
1810363678280

再看下面的代码:

a=(1, [2,3], 4)
b=a
#a[1]=20       #错:元祖a的元素是不可修改的
print(a[1])
print(id(a[1]))
a[1][0]=20       #a[1]元素是一个list对象,是可修改的
print(a[1])
print(id(a[1]))

因为元组a是不可修改的,即不可以修改其内容如元素a[1]的值,即“a[1]=20”将出错,但可以修改a[1]引用的那个可变list对象[2,3]。因此,“a[1][0]=20”是可以修改的。通过输出a[1]的id值,可以发现,作为元素a的元素,a[1]的值没有改变,仍然引用的是原先的list对象,但其引用的list对象内容发生了变化(通过打印a[1]可知道这一点)。以上修改a[1]引用的list对象的。过程如图2-2所示。

图2-2 修改a[1]引用的list对象

总结

● 数据类型分为可变类型和不可变类型,可变类型的对象的值是可修改的,而不可变类型的对象的值是不可修改的。

● 无论变量指向的是一个可修改的对象,还是一个不可修改的对象,给这个变量赋值都会使这个变量引用其他的对象(如创建的新对象)。对变量指向的可变对象的值进行修改不会使变量引用新对象,只是修改变量引用的对象的值。