【Python学习笔记】 第6章 动态类型

缺少声明语句的情况

我们没有声明变量的类型,但是当直接赋值时,Python知道应该把它转换为什么类型。(如a = 3,Python把它识别为整数)

变量,对象和引用

Python这么解释变量:

  • 变量创建:第一次给变量赋值时相当于创建变量,之后的赋值改变变量值。

  • 变量类型:变量不会拥有任何和它关联的类型信息和约束,它只是引用了一个特定的对象而已。

  • 变量使用:当变量出现在表达式中,它会被使用的对象代替。
    Python执行赋值语句(以a = 3为例)的步骤:

  1. 创建一个对象表示值3

  2. 如果第一次对a赋值,创建一个变量a

  3. 将变量a与对象3连接

实际效果如图所示:

具体而言,

  • 变量是一个系统表的入口,包含了指向对象的连接。
  • 在Python中,从变量到对象的连接被称为引用。
  • 对象被分配到的一块内存,有足够的空间表示它们所代表的值。
  • 引用是自动形成的从变量到对象的指针。

对象包含更复杂的信息,其中类型标志符标识这个对象的类型;引用的计数器决定何时收回对象。

类型属于对象,而不是变量

以下代码是可行的:

>>> a = 3
>>> a = 'spam'
>>> a = 1.23

这是因为,在Python中,变量没有类型。类型属于对象而不是变量名。因此,上述操作只是让变量引用了不同类型的对象而已。

但是,每个对象包含一个头部信息,标记了这个对象的类型。

因此,Python中的类型与对象相关联,而不是与变量关联。

对象的垃圾收集

如果原来的对象没有被引用的话,那么这个对象占用的空间被回收。在上述代码中,执行a = 'spam语句时,对象3没有被引用,因此它的空间被回收。

垃圾收集的工作原理是:Python为每个对象保留一个计数器,记录当前指向该对象的引用的数目。一旦这个计数器被设置为0,那么它的内存空间就会被回收。

在编写Python代码时,我们不需要手动实现垃圾收集。

关于Python垃圾回收的更多讨论

由于引用实现为指针,一个对象可能会引用自身,或者引用另一个引用了自身的对象。例:

>>> L = [1, 2]
>>> L.append(L)
>>> L
[1, 2, [...]]

这里L的第三个元素是指向自身的引用,因此它的计数器永远不会被清零。

共享引用

在交互式命令行下输入:

>>> a = 3
>>> b = a

结果如图所示:

这种情况被称为共享引用,即多个变量名引用同一个对象。

在此基础上,输入:

>>> a = 'spam'

它创建一个新对象(而不是改变对象3),并设置a对这个新对象进行引用,而b指向的值不会改变。


执行下面语句的时候a指向新的对象,而不是原来的、被修改过的对象。这是因为,整数是不可变的,不能在原位置修改它。

>>> a = 3
>>> b = a
>>> a = a + 2
>>> a, b
(5, 3)

共享引用和在原位置修改

有一些对象和操作可以在原位置改变对象。因此,对应这种类型的对象,共享引用时需要小心,因为对一个变量名的修改会影响其他变量。

如果给L1改变其引用对象的一个元素,由于这个对象与其他对象共享,那么这种修改也会影响程序的其他部分。

要避免这种情况,我们可以请求Python复制对象,而不是创建一样的引用。对于列表而言,分片操作就是创建一个新的变量:

>>> L1 = [2, 3, 4]
>>> L2 = L1[:]
>>> L1[0] = 24
>>> L1
[24, 3, 4]
>>> L2
[2, 3, 4]

此时L1L2指向不同的内存区域。对于其他核心类型(字典、集合),复制的方法是:使用X.copy()方法,或者将原有的对象传入它们的类型构造函数中,或者使用标准库的copy模块。其中copy模块提供了两种复制方式:深复制deepcopy和浅复制copy

共享引用和相等

在实际应用中,Python不一定回收对象,而是保存下来以便下一次重复利用。

基于Python的引用模型,我们有两种方法检查是否相等:

>>> L1 = [1, 2, 3]
>>> L2 = L1
>>> L3 = [1, 2, 3]
>>> L1 == L2, L1 == L3
(True, True)
>>> L1 is L2, L1 is L3
(True, False)

这里,==检查值是否相等;is检查对象的同一性(是否引用同一个对象)。如果值相等,但不是同一个值,那么is判定的返回值为False

但是,小的整数和字符串会被缓存并复用(而不是创建一个新的对象):

>>> X = 42
>>> Y = 42
>>> X == Y, X is Y
(True, True)

我们可以通过sys模块中的getrefcount返回对象的引用次数。

>>> import sys
>>> sys.getrefcount(1)
1000000207

动态类型随处可见

在Python中,任何东西看起来是通过赋值和引用工作的,而动态类型是Python的唯一的赋值模型。