3. 数据模型

优质
小牛编辑
142浏览
2023-12-01

3. 数据模型

3.1 对象、值和类型

对象是Python对数据的抽象。Python程序中所有数据都由对象或对象之间的关系表示。(合理且与冯.诺依曼的“存储程序计算机”模型一致,代码也由对象表示。)

每个对象都有一个ID,一个类型和一个值。对象一旦建立,它的ID永远不会改变;你可以认为它是该对象在内存中的地址。‘is’操作符比较两个对象的ID;id()函数返回一个表示对象ID 的整数(当前实现为对象的地址)。对象的类型也是不可变的。[1]对象的类型决定了对象支持的操作(例如,“它有长度吗?”)同时也定义了该种类型对象的可能的值。type()函数返回对象的类型(它本身也是一个对象)。某些对象的值可以改变。值可以改变的对象称为可变的;一旦建立,值就不可以改变的对象称为不可变的。(包含可变对象引用的不可变容器对象在可变对象改变时是可以改变的;但容器仍被看作是可变的, 因为它所包含的对象集合是不能变的。所以不可变对象与值不可变不是完全一样的,它更加微妙。)一个对象的可变性由它的类型决定;例如,数值、字符串和元组是不可变的,而字典和列表是可变的。

对象不可以显式地销毁;但是当它们不可用时可能被当作垃圾回收。具体的实现可以推迟垃圾回收或完全忽略它 — 这是垃圾回收如何实现的质量问题,只要依然能访问的对象不被回收。

CPython 实现细节:CPython 当前使用引用计数机制与(可选的)循环连接垃圾延迟检测机制,一旦对象变得不可访问,它将收集其中大部分,但是不保证收集包含循环引用的垃圾。参考gc 模块的文档可以获得控制循环垃圾回收的信息。其它实现的行为与之不同并且CPython 的实现将来也可能会变化。不要依赖对象不可访问后会立即终结(例如:永远关闭文件)。

注意使用具体实现的跟踪和调试工具可能会保持正常情况下可以回收的对象一直存活。还要注意使用‘try...except’语句捕获异常也可能保持对象一直存活。

有些对象包含“外部”资源的引用,例如打开的文件或窗口。可以理解在对象被当作垃圾回收时这些资源也被释放,但因为垃圾回收不保证一定发生,这样的对象也提供显式的方法释放外部资源,通常是close()方法。强烈建议程序显式关闭这些对象。‘try...finally’语句提供了一个便利的方式来做这件事。

有些对象包含其它对象的引用;它们叫做容器。容器的例子有元组,列表和字典。引用是容器的值的一部分。大多数情况下,当我们谈到一个容器的值时,我们是指值,而不是所包含的对象的ID;然而,当我们谈论容器对象的可变性的时候,就只是指被直接包含的对象的ID。因此,如果一个不可变对象(如元组)包含了一个可变对象的引用,那么当这个可变对象的值改变时它的值也发生改变。

对象的类型几乎影响对象的所有行为。在某种意义上甚至重要到影响对象的识别:对于不可变对象,计算新值的运算符可能实际上返回的是一个已存在的具有相同类型和值的对象的引用,而对于可变对象,这是不允许的。例如,在a=1;b=1之后,a 和b可能是或者可能不是引用同一个值为1的对象,这依赖于实现,但c=[];d=[]之后,c 和d可以保证是引用两个不同的、唯一的、新创建的空列表。(注意c=d=[]是把相同的对象赋给c 和d。)

3.2 标准类型的层次结构

以下是一份Python 内建类型的清单。扩展模块(无论是用C、Java还是用其它语言编写,依赖于具体实现)可以定义额外的类型。未来版本的Python 可能在这个类型层次机构中增加其它类型(例如:有理数、高效存储的整数数组,等等)。

下面有些类型的描述包含一个列出“特殊属性”的段落。这些属性提供访问具体的实现而不是作为一般的目的使用。它们的定义在未来可能会改变。

None 这种类型只有一个值。。只有一个对象具有这个值。这个对象通过内建名字None访问。它在许多情况下用来表示没有值,例如,没有显式返回任何内容的函数会返回它。它的真值为假。

NotImplemented 这种类型只有一个值。只有一个对象具有这个值。这个对象通过内建名字NotImplemented访问。如果数值方法和复杂的比较方法没有为提供的操作数实现某种运算,它们可能返回这个值。(解释器会尝试反射的操作,或者其它退化的操作,依赖于具体的运算符。)它的真值为真。

Ellipsis 这种类型只有一个值。只有一个对象具有这个值。这个对象通过内建名字Ellipsis访问。它用于指示切片中出现的...语法。它的真值为真。

numbers.Number 它们由数值字面量生成或者由算术运算符和内建的算术函数作为结果返回。数值对象是不可变的;一旦创建,它们的值永远不会改变。Python 的数值和数学上的数字关系当然是非常密切的,但受到计算机数值表达能力的限制。

Python 区分整数、浮点数和复数:

numbers.Integral 它们表示数学上的整数集中的元素(包括正数和负数)。

有三种类型的整数:

普通整数 它们表示在-2147483648 至2147483647 范围之间的数。(这个范围可能会在本地机器字较大的机器更大些,但不会更小。)如果某个运算的结果超出这个范围,结果会以长整数正常返回(在某些情况下,会抛出异常OverflowError)。对于以移位和掩码为目的的运算,整数采用32位或更多位的二进制补码形式,并且不会对用户隐藏任何位。(就是说,所有4294967296个不同的比特组合对应于不同的值)。

长整数 长整数的表示的数值范围没有限制,只受限于可用的(虚拟内存)内存。对于以移位和掩码为目的的运算,长整数采用二进制的形式,负数用二进制补码形式表示,给人的错觉是一个符号位向左无限扩展的字符串。

布尔值 布尔值表示假和真的真值。表示False 和True 的两个对象是仅有的布尔对象。布尔类型是普通整数的子类型,布尔值的行为在几乎所有环境下分别类似0和1,例外的情况是转换成字符串的时候分别返回字符串"False" 或"True"。

整数表示法的规则意在让负数的移位和掩码运算具有最有意义的解释,并且在普通整数和长整数之间转换时具有最少的意外。任何运算,只要它产生的结果在整数域之中,那么在长整数域或混合运算时将产生相同结果。域之间的转换对程序员是透明的。

numbers.Real (float) 这种类型表示机器级别的双精度浮点数。你受底层的机器体系结构(和C或者Java的实现)控制接受的范围和溢出处理。Python不支持单精度浮点数;使用它的原因通常是节省处理器和内存的使用,但是相比Python中对象使用的开销是微不足道的,因此没有必要支持两种浮点数使语言变的复杂。

numbers.Complex 这种类型以一对机器级别的双精度浮点数表示复数。单精度浮点数同样可以用于复数类型。复数z的实部和虚部可以通过只读属性z.real和z.imag获得。

序列 这种类型表示有限的顺序集合,用非负数索引。内建的函数len() 返回序列的元素个数。当序列的长度为n时,索引集合包含数字0, 1, ..., n-1。序列a的元素i 的选择使用a[i] 。

序列也支持切片:a[i:j]选择索引k满足i<=k<j的所有元素。作为表达式使用的时候,切片是一个相同类型的序列。这隐含着索引会从零开始重新计数。

某些序列还支持带有第三个“步长”参数的“扩展切片”:a[i:j:k] 选择a 中所有索引为x的 的元素,x=i+nk, n>=0 且i<=x<j*。

序列依据它们的可变性分为:

不可变序列不可变序列类型的对象一旦创建便不可改变。(如果这个对象包含其它对象的引用,这些引用的对象可以是可变的并且可以改变;然而不可变对象直接引用的对象集合不可改变。)

以下类型是不可变序列:

字符串 字符串的元素是字符。没有单独的字符类型;字符用一个元素的字符串表示。字符表示(至少)8 比特的字节。内建函数chr()ord()在字符和表示字节数值的非负整数之间转换。值在0-127 之间的字节通常表示相应的ASCII 值,但是对值的解释由程序决定。字符串数据类型也用于表示字节的数组,例如,保存从文件中读取的数据。

(在原生字符集不是ASCII 的系统上,字符串在内部可以使用EBCDIC 表示,只要函数chr()ord() 实现ASCII 和EBCDIC 之间的映射并且字符串的比较保留ASCII 顺序。或者可能有人能够提出一个更好的规则?)

Unicode Unicode 对象的元素是Unicode 编码单元。一个Unicode 编码单元由一个元素的Unicode 对象表示并且可以保持16位或者32位的值表示一个Unicode 序数。(序数的最大值在sys.maxunicode中给出,并依赖Python 在编译的时候是如何配置的)Unicode 对象中可以表示代理对,并被当作两个单独的元素。内建的函数unichr()ord()在编码单元和表示定义在Unicode 标准3.0中Unicode 序数的非负整数之间转换。和其它编码之间相互转换可以通过Unicode 方法encode() 和内建的函数unicode()

元组 元组的元素可以是Python 的任何对象。两个或多个元素的元组由逗号分隔的一连串表达式形成。一个元素的元组(单元素集)可以在一个表达式的后面附加一个逗号形成(一个表达式自身不会形成一个元组,因为圆括号必须可以用来分组表达式)。一个空的元组可以由一个空的圆括号对形成。

可变序列 可变序列在生成之后可以修改。下标和切片表示法可以用于赋值和del (delete)语句的对象。

当前有两种内建的可变序列类型:

列表 列表的对象可以是Python 任何对象。列表由在方括号中放置一个逗号分隔的一连串表达式形成。(注意生成长度为0或1的列表没有特殊的情形。)

字节数组 一个字节数组对象是一个可变的数组。它们由内建的bytearray() 构造函数创建。除了可变性(因此不可哈希),另一方面字节数组提供和不可变字节对象同样的接口和功能。

扩展模块array提供另外一个可变序列类型的例子。

集合类型 这种类型表示无序的、有限的集合,集合中的元素是唯一的、不可变的对象。正因如此,它们不可以被任何下标索引。然而,它们可以迭代,内建函数len()返回集合中元素的个数。集合常见的用途有快速成员关系检测、从序列中删除重复元素和计算数学运算例如交集、并集、差集和对称差集。

集合的元素与字典的键一样,都适用不可变规则。注意,数值类型遵循正常的数值比较规则:如果两个数字相等(例如,1 和1.0),其中只有一个可以包含在集合中。

当前有两种内建的集合类型:

集合 这种类型表示可变的集合。它们由内建函数set() 构造函数创建并可以在此之后通过几种方法修改,例如add()

固定集合 这种类型表示不可变集合。它们由内建函数frozenset()构造器创建。因为固定集合不可变且可以哈希,它可以作为另外一个集合的元素或者字典的键。

映射 这种类型表示由任意索引集合作索引的有限对象集合。下标表示法a[k] 从映射a 中选择由k 索引的元素;它可以用在表达式中并作为赋值或del语句的目标。内建函数len()返回映射中元素的个数。

当前只有一个内建映射类型:

字典 这种类型表示几乎可以由任何值索引的有限对象集合。不可以作为键的唯一类型是包含列表或者字典或者其它可变类型的值,这些类型通过值而不是对象ID比较,原因是字典的高效实现要求键的哈希值保持常量。注意,数值类型遵循正常的数值比较规则:如果两个数字相等(例如,1 和1.0),那么它们可以互换地使用来索引同一个字典入口。

字典是可变的;它们可以通过{...} 表示法创建(参考Dictionary的显示一节)。

扩展模块dbmgdbmbsddb提供另外的映射类型的例子。

可调用类型 这是一种可以使用函数调用操作(参考Calls一节)的类型:

用户定义的函数 用户定义的函数对象由函数定义创建(参见函数定义一节)。它调用时参数列表的元素个数应该和函数的形式参数列表相同。

特殊属性:

属性含义
docfunc_doc函数的文档字符串,如果没有就为None。可写
namefunc_name函数的名字。可写
module函数定义所在的模块名,如果没有就为None。可写
defaultsfunc_defaults为具有默认值的参数保存默认参数值的元组,如果没有参数具有默认值则为None。可写
codefunc_code表示编译后的函数体的代码对象。可写
globalsfunc_globals保存函数全局变量的字典的引用 — 函数定义所在模块的全局命名空间。只读
dictfunc_dict支持任意函数属性的命名空间。可写
closurefunc_closureNone 或者包含函数自由变量绑定的元组。只读

大部分标有“可写”的属性会检查所赋值的类型。

版本2.4中的变化:func_name 成为如今可写的属性。

版本2.6中的变化:引入双下划线属性closure, code, defaults, 和globals作为对应的func_*属性的别名以向前兼容Python 3。

函数对象同样支持获取和设置任意属性,这可以用来附加元数据到函数中。常规属性可以用点号表示法获取和设置。注意当前的实现只在用户定义的函数上支持函数属性。未来可能支持内建函数上的函数属性。

函数定义的额外信息可以从它的代码对象中获取;参见下面内部类型的描述。

用户定义的方法 用户定义的方法将类、类的实例(或者None)和任何可调用对象(通常是一个用户定义的函数)结合起来。

特殊的只读属性:imself指类实例对象,imfunc指函数对象;imclass对于绑定的方法指imself的类,对于未绑定的方法指方法所在的类;doc指方法的文档(与imfunc.doc相同);_name指方法的名字(与im_func.__name一样);__module指方法定义所在模块的名字,如果没有则为None。

版本2.2中的变化:im_self过去指的是定义方法的类。

版本2.6中的变化:为了Python 3向前的兼容性,imfunc也可以使用func访问,imself可以使用__self访问。

方法也支持访问(但不能设置)底层函数对象的任何函数属性。

用户定义的方法对象可能在获取类的一个属性的时候创建(可能通过类的一个实例),如果那个属性是用户定义的函数对象或者一个未绑定的用户方法对象或者一个类方法对象。如果那个属性是用户定义的方法对象,只有类和存储在原始方法对象中的类或其子类相同,才会创建一个新的方法对象;否则,使用初始的方法对象。

如果用户定义的方法是通过从类中获取用户定义的方法创建,它的im_self为None并且方法对象称为