Python:动态类型简介

下一节:python字符串

1. 缺少类型声明语句的情况

在 Python 中,类型是在运行过程中自动决定的,而不是通过代码声明。

1.1 变量、对象和引用

  • 变量创建:一个变量,就像 a,当代码第一次给它赋值时就创建了它。之后的赋值将会改变已创建的变量名的值。
  • 变量类型:变量永远不会有任何和它关联的类型信息或约束。类型的概念是存在于对象中而不是变量名中。
  • 变量使用:当变量出现在表达式中时,它马上被当前引用的对象所代替,无论这个对象是什么类型。所有的变量必须在其使用前明确地赋值,使用未赋值的变量会产生错误。

从概念上说,对 a = 3,Python 将会执行三个不同的步骤去完成请求:

  1. 创建一个对象来代表值 3。
  2. 创建一个变量 a,如果它还没有创建的话。
  3. 将变量与新的对象 3 相连接。

变量和对象保存在内存中的不同部分,并通过连接相关联。

Python 中从变量到对象的连接称为引用。引用是一种关系,以内存中的指针的形式实现。

  • 变量是一个系统表的元素,拥有指向对象的连接的空间。
  • 对象是分配的一块内存,有足够的空间去表示它们所代表的的值。
  • 引用是自动形成的从变量到对象的指针。

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

在 Python 中,变量名没有类型,类型属于对象,而不是变量名。Python 的变量就是在特定的时间引用了一个特定的对象。

1
a = 3           # 整数
1
a = 'spam'      # 现在是一个字符串
1
a = 1.23        # 现在是一个浮点数

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

1.3 对象的垃圾收集

每当一个变量名被赋予了一个新的对象,之前的那个对象占用的空间就会被回收(如果它没有被其他的变量名或对象所引用的话)。这种自动回收对象空间的技术叫做垃圾回收。

在内部,Python 在每个对象中保持了一个计数器,计数器记录了当前指向该对象的引用的数目。一旦(并精确在同一时间)这个计数器被设置为零,这个对象的内存空间就会自动回收。

2. 共享引用

1
2
a = 3
b = a

赋值语句 b = a 之后,变量 b 成为对象 3 的一个引用,即 a 和 b 都引用了相同的对象。这叫做共享引用。

1
2
3
4
a = 3
b = a
a = 'spam'
b
3

对于上例,简单地创建了一个新的对象(‘spam’),并设置 a 对这个新的对象进行引用。这并不会改变 b 的值,b 仍然引用原始的对象。

ref.png

给一个变量赋一个新的值,并不是替换了原始的对象,而是让这个变量去引用完全不同的一个对象。当可变对象以及原处改变进入这个场景,那么这个情形会有某种改变。

2.1 共享引用和在原处修改

对于支持原处修改的对象,共享引用时需要加倍小心,因为对一个变量名的修改会影响其他的变量。

例如列表支持对位置的在原处赋值:

1
2
3
4
L1 = [2, 3, 4]
L2 = L1
L1 = 24
L1, L2
(24, [2, 3, 4])

L1 直接设置为一个不同的对象,L2 仍是引用最初的列表。

1
2
3
4
L1 = [2, 3, 4]          # 可变对象
L2 = L1 # 创建一个相同对象的引用
L1[0] = 24 # 进行原处修改
L1, L2
([24, 3, 4], [24, 3, 4])

这里改变了 L1 所引用的对象的一个元素。这类修改会覆盖列表对象中的某部分。

因为这个列表对象是与其他对象共享的,那么一个像这样在原处的改变不仅仅会对 L1 有影响。

这种行为仅在支持原处修改的可变对象上发生,如果不想要这样的现象发生,可以拷贝对象,而不是创建引用。

1
2
3
4
L1 = [2, 3, 4]
L2 = L1[:] # 创建一个 L1 的拷贝
L1[0] = 24
L1, L2
([24, 3, 4], [2, 3, 4])

这种分片技术不会应用在其他的可变的核心类型(字典和集合)上,复制一个字典或集合应该使用 X.copy() 方法调用。

1
2
3
import copy
X = copy.copy(Y) # 创建任意对象 Y 的顶层“浅”拷贝
X = copy.deepcopy(Y) # 创建任意对象 Y 的深拷贝:拷贝所有嵌套部分

2.2 共享引用和相等

在 Python 中有两种不同的方法去检查是否相等:

1
2
3
L = [1, 2, 3]
M = L # 创建共享引用
L == M # 值相等
True
1
L is M                  # 相同对象
True

“== 操作符”,测试两个被引用的对象是否具有相同的值。

“is 操作符”,检查对象的同一性,是否精确地指向同一个对象。可以是检测共享引用的一种办法。

1
2
3
L = [1, 2, 3]
M = [1, 2, 3]
L == M
True
1
L is M                  # 不同的对象
False
1
2
3
X = 42
Y = 42
X == Y
True
1
X is Y
True

因为小的整数和字符串被缓存并复用,所以 X 和 Y 引用了一个相同的对象。