3.5 动态类型

3.5.1 变量

在C、C++语言中,如果我们需要使用变量,需要先对变量类型进行声明,而在Python中,我们既不用说明变量的类型,也不用声明变量存在。但是变量在使用前必须被赋值。

对于变量x来说,它在第一次被赋值时创建。也可以说通过赋值,我们创建了变量x。对于变量x来说,变量一旦被创建,并不意味着它的类型就固定了。进一步说,变量永远不会被类型固定,变量永远是通用的。变量通过引用特定的对象,可以改变自身数据类型。

3.5.2 引用

我们创建的变量都是对对象的引用。要想深入理解这句话的含义,先让我们考虑这样一种情形:为变量x赋值0。

>>>x=0

Python解释器在完成对变量x赋值的过程中,一共经历了三步:

  1. 创建一个值为3的整数对象。

  2. 创建一个变量x。如果变量x已经存在,则无需执行此步。

  3. 使变量引用在第一步中创建的整数对象。

    变量总是引用对象,而不会出现引用到变量的情况。

    引用是一种关系,这种关系通过内存中的指针来实现。当创建变量的时候,解释器就会自动追踪这个变量的引用对象。

    现在我们对x进行多次赋值:

>>>x=0

>>>x={1, 2 , 3, 4}

>>>x=[1, 2]

>>>x=”hello world!”

我们首先创建了一个值为0的整数对象,然后创建了一个变量x,使x引用这个整数对象。接着我们使x引用含有元素1, 2, 3, 4的集合对象。最后我们使x引用字符串对象“hello world!”。在这个例子中,我们通过对x进行多次赋值,使x多次引用了不同的对象类型。这也从侧面反映了,变量本身并不对类型进行记忆,实质上,类型在对象中存放。

在Python中,每一个对象都有两个标准的头部信息:类型标识符(type desinator)和引用计数器(reference counter)。前者标识了对象的类型,后者记录对对象进行回收的时间。

3.5.3 垃圾回收

在引用一节中,我们对x进行多次赋值,使其引用不同的对象类型,最终使其指向字符串对象类型。这样的话,我们就有一个疑问:之前我们创建的整型对象,集合对象和列表对象到哪去了?

实质上,这些没有被引用的对象占用的内存被回收了,所以这些对象自然也就消失了。这种自动回收对象空间的技术叫做垃圾回收。每当x引用新的对象时,解释器都会对旧对象占用的内存进行回收。这些就空间被放入自由内存空间池,可以被再次利用。

对象中的引用计数器记录了当前引用该对象的变量的数目。一旦这个计数器变为零,就意味着,不再有变量指向这个对象。这时候,解释器就会对这个对象占用的内存空间进行回收。

3.5.4 共享引用

当多个变量共同引用一个对象时,就构成了共享引用。如果这个对象不支持在原位置修改,那么对变量进行重新赋值就会使变量重新引用新的变量。当所有变量不再引用这个对象时,该对象才会被回收。如果这个对象支持在原位置进行修改,对变量进行原位赋值,就会使这个对象发生改变从而影响到所有变量。

例:共享整型引用对象引用

>>>x=0

>>>y=x

>>>x, y

>>>x=1

>>>x, y

在上例中,我们先使变量x和y共享为0的整型对象, 然后对x赋值1。此时,x引用了新的值为1的整型变量,y依然引用原有的值为0的整型变量。

我们可以使用身份运算符来确认多变量是否引用到了同一个对象:

例:共享列表引用对象,同时使用身份运算符对判断变量是否引用到同一个对象

>>>L1=[1, 2, 3]

>>>L2=L1

>>>L1, L2

([1, 2, 3], [1, 2, 3])

>>>L1=4

>>>L1, L2, L1 is L2

(4, [1, 2, 3], False)

>>>L1=L2

>>>L2[0]=4

>>>L1, L2, L1 is L2

([4, 2, 3], [4, 2, 3], True)

在上例中,我们首先使L1,L2共享引用列表对象[1, 2, 3],然后对L1赋值4。此时,L1引用了新的值为4的整型变量,y依然引用原有列表对象[1, 2, 3]。之后,我们使L1重新引用列表对象,这时L1和L2再次构成了共享引用。之后,我们对变量L2的第0个位置的元素赋值为4。这是,L1和L2依然引用同一个列表对象,不同的是此列表对象的第0个位置的元素值发生了改变,从1变为4。这种改变影响到了所有引用此列表的变量。之所以出现这种情况,是因为列表对象支持在原位置改变,而整数对象则不支持。

Last updated