4.8 标准类型的分类

如果让我们最啰嗦地描述标准类型,我们也许会称它们是Python的“基本内建数据对象原始类型”。

  • “基本”是指这些类型都是Python提供的标准或核心类型。

  • “内建”是由于这些类型是Python默认就提供的。

  • “数据”是因为他们用于一般数据存储。

  • “对象”是因为对象是数据和功能的默认抽象。

  • “原始”是因为这些类型提供的是最底层的粒度数据存储。

  • “类型”是因为他们就是数据类型。

不过,上面这些描述实际上并没有告诉你每个类型如何工作,以及它们能发挥什么作用。事实上,几个类型共享某一些的特性,比如功能的实现手段,另一些类型则在访问数据值方面有一些共同之处。我们感、兴趣的还有这些类型的数据如何更新,以及它们能提供什么样的存储。有3种不同的模型可以帮助我们对基本类型进行分类,每种模型都展示给我们这些类型之间的相互关系。这些模型可以帮助我们更好的理解类型之间的相互关系以及他们的工作原理。

4.8.1 存储模型

我们对类型进行分类的第一种方式,就是看着这种类型的对象能保存多少个对象。Python的类型,就像绝大多数其他语言一样,能容纳一个或多个值。一个能保存单个字面对象的类型,我们称它为原子或标量存储;那些可容纳多个对象的类型,我们称之为容器存储(容器对象有时会在文档中被称为复合对象,不过这些对象并不仅仅指类型,还包括类似类实例这样的对象)。容器类型又带来一个新问题,那就是它是否可以容纳不同类型的对象。所有的Python容器对象都能够容纳不同类型的对象。表4.6按存储模型对Python的类型进行了分类。

字符串看上去像一个容器类型,因为它“包含“字符(并且经常多于一个字符),不过由于Python并没有字符类型(参见章节4.8),所以字符串是一个自我包含的文字类型。

4.8 标准类型的分类 - 图1

4.8.2 更新模型

另一种对标准类型进行分类的方式就是,针对每一个类型问一个问题:“对象创建成功之后,它的值可以进行更新吗?”在前面我们介绍Python数据类型时曾经提到,某些类型允许他们的值进行更新,而另一些则不允许。可变对象允许他们的值被更新,而不可变对象则不允许他们的值被更改。表4.7列出了支持更新和不支持更新的类型。

看完这个表之后,你可能马上冒出一个问题: “等等,你说数值和字符串对象是不可改变的?看看下面的例子!”

4.8 标准类型的分类 - 图2

“在我看来,这可不像是不可变对象的行为!”没错,是这样,不过你还没有搞清楚幕后的真相。上面的例子中,事实上是一个新对象被创建,然后它取代了旧对象。就是这样,请多读一遍这段。

新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。你可以通过内建函数id()来确认对象的身份在两次赋值前后发生了变化。

4.8 标准类型的分类 - 图3

下面我们在上面的例子里加上id()调用,就会清楚地看到对象实际上已经被替换了。

4.8 标准类型的分类 - 图4

你看到的身份数字很可能和我不同,每次执行这些数字也会不同,这是正常的。这个数字与该对象当时分配的内存地址密切相关。因此不同的机器、不同的执行时间都会生成不同的对象身份。另一类对象,列表可以被修改而无须替换原始对象,请看下面的例子。

4.8 标准类型的分类 - 图5

注意列表的值不论怎么改变,列表的ID始终保持不变。

4.8.3 访问模型

尽管前面两种模型分类方式在介绍Python时都很有用,它们还不是区分数据类型的首要模型。对这种目的,我们使用访问模型。也就是说根据访问我们存储的数据的方式对数据类型进行分类。在访问模型中共有三种访问方式:直接存取、顺序和映射。表4.8按访问方式对数据类型进行了分类。

对非容器类型可以直接访问。所有的数值类型都归到这一类。

序列类型是指容器内的元素按从0开始的索引顺序访问。一次可以访问一个元素或多个元素,也就是大家所了解的切片(slice)。字符串、列表和元组都归到这一类。我们前面提到过,Python不支持字符类型,因此,虽然字符串是简单文字类型,但因为它有能力按照顺序访问子字符串,所以也将它归到序列类型。

映射类型类似序列的索引属性,不过它的索引并不使用顺序的数字偏移量取值,它的元素无序存放,通过一个唯一的键来访问,这就是映射类型,它容纳的是哈希键-值对的集合。

我们在以后的章节中将主要使用访问模型,详细介绍各种访问模型的类型,以及某个分类的类型之间有哪些相同之处(比如操作符和内建函数),然后讨论每种Python标准类型。所有类型的特殊操作符、内建函数及方法都会在相应的章节特别说明。

为什么要对同样的数据类型再三分类呢?首先,我们为什么要分类?因为Python提供了高级的数据结构,我们需要将那些原始的类型和功能强大的扩展类型区分开来。另一个原因就是这有助于搞清楚某种类型应该具有什么行为。举例来说,如果我们基本上不用问自己“列表和元组有什么区别?”或“什么是可变类型和不可变类型?”这些问题的时候,我们也就达到了目的。最后,某些分类中的所有类型具有一些相同的特性。一个优秀的工匠应该知道他或她的工具箱里都有哪些宝贝。

4.8 标准类型的分类 - 图6

4.8 标准类型的分类 - 图7

另一个问题就是,“为什么要用这么多不同的模型或从不同的方面来分类?”所有这些数据类型看上去是很难分类的。它们彼此都有着错综复杂的关系,所有类型的共同之处最好能揭示出来,而且我们还想揭示每种类型的独到之处。没有两种类型横跨所有的分类(当然,所有的数值子类型做到了这一点,所以我们将它们归纳到一类当中)。最后,我们确信搞清所有类型之间的关系会对你的开发工作有极大的帮助。你对每种类型的了解越多,你就越能在自己的程序中使用恰当的类型以达到最佳的性能。

我们提供了一个汇总表(表4.9)。表中列出了所有的标准类型和我们使用的三个模型,以及每种类型归入的分类。