Mutable vs. Immutable

Table of Contents


这里介绍一下,python 中的可变数据类型和不可变数据类型。

当一个对象被初始化的时候,这个对象会被分配一个唯一的 id,用来标示它在内存中的位置。 这里的可变和不可变对象指的是在对象 id(对应到一块内存区域)不变的情况下,一个对象的内容能否改变。

id

id() 是 python 内置函数,它返回一个数字类型,为一个对象的标示。

下面我们看一些例子:

a = 'Hello World'
b = 'Hello World'

id(a)
id(b)

a = 'Hello'
id(a)

输出的内容为:

4450937520
4450937616
4450864064

这里第一个和第二个对应的是同一个字符串,它们的 id 竟然也不同,但是有些情况是相同的。关于这部分可以再写一些东西了(这个坑以后补上)。 这里我们重点看下,第三个和第一个是不同的,因为 a 是不可变对象,如果对它赋值,相当于创建一个新的对象。

我们在看下 list 对象的表现:

l = [1, 2, 3]
id(l)
l[0] = 4
id(l)

下面输出的结果是:

4451063856
4451063856

这里我们对 l 的内容做了修改,但是它的 id 值没有改变。

可变与不可变对象

可变对象 不可变对象
list int
dict float
set string
byte array tuple
  frozen set
  bytes
  complex

上面列举的是可变可不可变的对象,但是不可变对象是永远不可变吗?其实不是的,下面举一个例子。 对于 tuple 本身来说,它是不可变的,但是如果 tuple 里面的 element 是一个可变的对象呢?

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

上面的两个 id 的输出结果是一样的。这里我们需要特别的注意。

函数传值方式

在函数参数传递的时候,它们的区别可能表现的更加明显。

简单来说,就是不可变对象传的是值,而可变对象传的是引用。其实本质上它们应该都是传引用,但是对于不可变对象来说,它做不到啊,因为我们没有办法在它原来的内存空间去更新它的内容,只能创建一个新的对象,这种表现其实就是传值了。

对于可变对象来说,这里就有些复杂了。下面看个例子:

a = [1]
def hello(a = []):
        print("id:", id(a))
        a.append('test')
        print(a)

我们通过两种方式去调用它,

hello() # 使用默认参数
hello(a)

它们的 id 其实是不同的,因为第一种情况,a 的生存期只是在函数内部,在外部定义的 a 和它没有任何的关系。 在使用 hello(a)的时候,通过 append 操作,函数外边定义的 a 也发生了改变也就是说它传递的是引用。