python浓缩(4)Python 对象

  • 什么是Python 对象;

  • 最常用的内建类型;

  • 标准类型运算符和内建函数;

  • 标准类型的不同分类方式;

  • Python 目前还不支持的类型(这对那些有其他高级语言经验的人会有所帮助)。

4.1 Python 对象

Python 使用对象模型来存储数据。构造任何类型的值都是一个对象。

所有的Python 对象都拥有三个特性:身份,类型和值。

(1)身份:

每一个对象都有一个唯一的身份标识自己,任何对象的身份可以使用内建函数id()来得到。这个值可以被认为是该对象的内存地址

(2)类型:

对象的类型决定了该对象可以保存什么类型的值,可以进行什么样的操作,以及遵循什么样的规则。您可以用内建函数type()查看Python 对象的类型。因为在Python 中类型也是对象,所以type()返回的是对象而不是简单的字符串。

(3)值:

对象表示的数据项

除了值之外,其它两个特性都是只读的。对于新风格的类型和类, 对象的类型也是可以改变的,不过对于初学者并不推荐这样做

对象的值是否可以更改被称为对象的可改变性(mutability)。只要一个对象还没有被销毁, 这些特性就一直存在。

Python 有一系列的基本(内建)数据类型,必要时也可以创建自定义类型来满足你的应用程序的需求。绝大多数应用程序通常使用标准类型,对特定的数据存储则通过创建和实例化类来实现。

4.1.1 对象属性

某些Python 对象有属性、值或相关联的可执行代码,比如方法(method)。Python 用点(.)标记法来访问属性。属性包括相应对象的名字等等。最常用的属性是函数和方法,不过有一些Python 类型也有数据属性。含有数据属性的对象包括(但不限于):类、类实例、模块、复数和文件。

4.2 标准类型

? 数字(分为几个子类型,其中有三个是整型)

? 整型

? 布尔型

? 长整型

? 浮点型

? 复数型

? 字符串

? 列表

? 元组

? 字典

在本书中,我们把标准类型也称作“基本数据类型”,因为这些类型是Python 内建的基本数据类型,我们会在第5、6 和7 章详细介绍它们。

4.3 其他内建类型

? 类型

? Null 对象 (None)

? 文件

? 集合/固定集合

? 函数/方法

? 模块

? 类

这些是当你做Python 开发时可能会用到的一些数据类型。我们在这里讨论Type 和None类型的使用,除此之外的其他类型将在其他章节中讨论。

4.3.1 类型对象和type 类型对象

对象的一系列固有行为和特性必须事先定义好。从这个角度看,类型正是保存这些信息的最佳位置。描述一种类型所需要的信息不可能用一个字符串来搞定,所以类型不能是一个简单的字符串,这些信息不能也不应该和数据保存在一起, 所以我们将类型定义成对象。

通过调用type()函数你能够得到特定对象的类型信息:

>>> type(42)
<type 'int'>

请注意看 type 函数有趣的返回值。我们得到一个简洁的输出结果<type 'int'>。不过你应当意识到它并不是一个简简单单的告诉你 42 是个整数这样的字符串。您看到的<type 'int'>实际上是一个类型对象,碰巧它输出了一个字符串来告诉你它是个int 型对象。

那么类型对象的类型(“类型对象”的对象)是什么?:

>>> type(type(42))
<type 'type'>

所有类型对象的类型都是type,它也是所有Python 类型的根和所有Python 标准类的默认元类(metaclass)。

类型对象在面向对象编程和日常对象使用中扮演着更加重要的角色。从现在起, 类就是类型,实例是对应类型的对象

4.3.2 None, Python 的 Null 对象

Python 有一个特殊的类型,被称作 Null 对象或者 NoneType,它只有一个值,那就是 None。它不支持任何运算也没有任何内建方法。None 没有什么有用的属性,它的布尔值总是False。

核心笔记:布尔值

所有标准对象均可用于布尔测试,同类型的对象之间可以比较大小。每个对象天生具有布尔 True 或 False 值。

空对象、值为零的任何数字或者Null 对象 None 的布尔值都是False。

下列对象的布尔值是False。

? None

? False (布尔类型)

? 所有的值为零的数:

? 0 (整型)

? (浮点型)

? 0L (长整型)

? 0.0+0.0j (复数)

? "" (空字符串)

? [] (空列表)

? () (空元组)

? {} (空字典)

值不是上面列出来的任何值的对象的布尔值都是 True,例如non-empty、 non-zero 等等。

用户创建的类实例如果定义了nonzero(__nonzero__())或length(__len__())且值为0,那么它们的布尔值就是 False。

4.4 内部类型

? 代码

? 帧

? 跟踪记录

? 切片

? 省略

? Xrange

我们在这里简要介绍一下这些内部类型,一般的程序员通常不会直接和这些对象打交道。请参阅源代码或者Python 的内部文档和在线文档获得更详尽的信息。

你如果对异常感到迷惑的话,可以告诉你它们是用类来实现的,在老版本的Python 中,异常是用字符串来实现的。

4.4.1 代码对象

代码对象编译过的Python源代码片段,它是可执行对象。通过调用内建函数compile()可以得到代码对象。代码对象可以被 exec 命令或 eval()内建函数来执行。在第14 章将详细研究代码对象。

代码对象本身不包含任何执行环境信息, 它是用户自定义函数的核心, 在被执行时动态获得上下文。(事实上代码对象是函数的一个属性)一个函数除了有代码对象属性以外,还有一些其它函数必须的属性,包括函数名,文档字符串,默认参数,及全局命名空间等等。

4.4.2 帧对象

帧对象表示 Python 的执行栈帧帧对象包含Python 解释器在运行时所需要知道的所有信息。它的属性包括指向上一帧的链接,正在被执行的代码对象(参见上文),本地及全局名字空间字典以及当前指令等。每次函数调用产生一个新的帧,每一个帧对象都会相应创建一个C 栈帧。用到帧对象的一个地方是跟踪记录对象(参见下一节)

4.4.3 跟踪记录 对象

当你的代码出错时, Python 就会引发一个异常。如果异常未被捕获和处理, 解释器就会退出脚本运行,显示类似下面的诊断信息:

Traceback (innermost last):
File "<stdin>", line N?, in ???
ErrorName: error reason

当异常发生时,一个包含针对异常的栈跟踪信息的跟踪记录对象被创建。如果一个异常有自己的处理程序,处理程序就可以访问这个跟踪记录对象

4.4.4 切片对象

当使用Python 扩展的切片语法时,就会创建切片对象。扩展的切片语法允许对不同的索引切片操作,包括步进切片, 多维切片,及省略切片。

多维切片语法是 sequence[start1 : end1,start2 : end2], 或使用省略号, sequence[...,start1 : end1 ]. 切片对象也可以由内建函数 slice()来生成

步进切片允许利用第三个切片元素进行步进切片,它的语法为sequence[起始索引 : 结束索引 : 步进值]。下面是几个步进切片的例子:

>>> foostr = 'abcde'
>>> foostr[::-1]
'edcba'
>>> foostr[::-2]
'eca'
>>> foolist = [123, 'xba', 342.23, 'abc']
>>> foolist[::-1]
['abc', 342.23, 'xba', 123]
4.4.5 省略对象

省略对象用于扩展切片语法中起记号作用。 这个对象在切片语法中表示省略号。类似Null 对象 None, 省略对象有一个唯一的名字 Ellipsis, 它的布尔值始终为 True.

4.4.6 XRange 对象

调用内建函数 xrange() 会生成一个Xrange 对象,xrange()是内建函数 range()的兄弟版本, 用于需要节省内存使用或 range()无法完成的超大数据集场合。在第8 章你可以找到更多关于 range() 和 xrange() 的使用信息。

4.5 标准类型运算符

4.5.1 对象值的比较

比较运算符用来判断同类型对象是否相等,所有的内建类型均支持比较运算,比较运算返回布尔值 True 或 False。

注意,实际进行的比较运算因类型而异。换言之,数字类型根据数值的大小和符号比较,字符串按照字符序列值进行比较,等等。

>>> 5+4j >= 2-3j
True
>>> 'abc' == 'xyz'
False
>>> 'abc' > 'xyz'
False
>>> 'abc' < 'xyz'
True
>>> [3, 'abc'] == ['abc', 3]
False
>>> [3, 'abc'] == [3, 'abc']
True

不同于很多其它语言,多个比较操作可以在同一行上进行,求值顺序为从左到右。

>>> 3 < 4 < 7 # same as ( 3 < 4 ) and ( 4 < 7 )
True
>>> 4 > 3 == 3 # same as ( 4 > 3 ) and ( 3 == 3 )
True
>>> 4 < 3 < 5 != 2 < 7
False

我们会注意到比较操作是针对对象的值进行的,也就是说比较的是对象的数值而不是对象本身。在后面的部分我们会研究对象身份的比较。

表4.1 标准类型值比较运算符

运算符 功能

expr1 < expr2 expr1 小于 expr2

expr1 > expr2 expr1 大于 expr2

expr1 <= expr2 expr1 小于等于 expr2

expr1 >= expr2 expr1 大于等于 expr2

expr1 == expr2 expr1 等于 expr2

expr1 != expr2 expr1 不等于 expr2 (C 风格)

expr1 <> expr2 expr1 不等于 expr2 (ABC/Pascal 风格)

注: 未来很有可能不再支持 <> 运算符,建议您一直使用 != 运算符。

4.5.2 对象身份比较

作为对值比较的补充,Python 也支持对象本身的比较。对象可以被赋值到另一个变量(通过引用)。因为每个变量都指向同一个(共享的)数据对象,只要任何一个引用发生改变,该对象的其它引用也会随之改变。

为了方便大家理解,最好先别看变量的值,而是将变量名看作对象的一个链接。让我们来看以下三个例子:

例1: foo1 和 foo2 指向相同的对象

foo1 = foo2 = 4.3

它表现的只是一个多重赋值,将4.3 这个值赋给了foo1和foo2 这两个变量。不过它还有另一层含义:一个值为4.3 的数字对象被创建,然后这个对象的引用被赋值给foo1 和foo2, 结果就是 foo1 和 foo2 指向同一个对象。

例2: foo1 和 foo2 指向相同的对象

foo1 = 4.3
foo2 = foo1

这个例子非常类似上一个,一个值为4.3 的数值对象被创建,然后赋给一个变量, 当执行foo2 = foo1 时, foo2 被指向foo1 所指向的同一个对象, 这是因为Python 通过传递引用来处理对象。foo2 就成为原始值4.3 的一个新的引用。 这样foo1 和foo2 就都指向了同一个对象。

例3: foo1 和 foo2 指向不同的对象

foo1 = 4.3
foo2 = 1.3 + 3.0

这个例子有所不同。首先一个数字对象被创建,然后赋值给foo1. 然后第二个数值对象被创建并赋值给foo2. 尽管两个对象保存的是同样大小的值,但事实上系统中保存的都是两个独立的对象,其中foo1 是第一个对象的引用, foo2 则是第二个对象的引用。图4-2 演示给我们这里有两个不同的对象,尽管这两个对象有同样大小的数值。 我们为什么在示意图中使用盒子?没错,对象就象一个装着内容的盒子。当一个对象被赋值到一个变量,就象在这个盒子上贴了一个标签,表示创建了一个引用。每当这个对象有了一个新的引用,就会在盒子上新贴一张标签。当一个引用被销毁时, 这个标签就会被撕掉。当所有的标签都被撕掉时, 这个盒子就会被回收。那么,Python 是怎么知道这个盒子有多少个标签呢?

113641_dkZr_724288.png

每个对象都天生具有一个计数器,记录它自己的引用次数。这个数目表示有多少个变量指向该对象。Python 提供了is 和is not运算符来测试两个变量是否指向同一个对象。象下面这样执行一个测试:

a is b

这个表达式等价于下面的表达式

id(a) == id(b)

对象身份比较运算符is is not 拥有同样的优先级,表4.2 列出了这些运算符。我们创建了一个变量,然后将第二个变量指向同一个对象。

>>> a = [ 5, 'hat', -9.3]
>>> b = a
>>> a is b
True
>>> a is not b
False
>>> b = 2.5e-5
>>> b
2.5e-005
>>> a
[5, 'hat', -9.3]
>>> a is b
False
>>> a is not b
True

表4.2 标准类型对象身份比较运算符

obj1 is obj2 obj1 和obj2 是同一个对象

obj1 is not obj2 obj1 和obj2 不是同一个对象

在上面的例子中,您会注意到我们使用的是浮点数而不是整数。为什么这样?整数对象和字符串对象是不可变对象所以Python 会很高效的缓存它们。这会造成我们认为Python 应该创建新对象时,它却没有创建新对象的假象。看下面的例子:

>>> a = 1
>>> id(a)
8402824
>>> b = 1
>>> id(b)
8402824
>>> c = 1.0
>>> id(c)
8651220
>>> d = 1.0
>>> id(d)
8651204

在上面的例子中,a 和 b 指向了相同的整数对象,但是 c 和 d 并没有指向相同的浮点数对象。如果我们是纯粹主义者,我们会希望 a 与 b 能和 c 与 d 一样,因为我们本意就是为了创建两个整数对象,而不是像 b = a 这样的结果。

Python 仅缓存简单整数,因为它认为在Python 应用程序中这些小整数会经常被用到。当我们在写作本书的时候,Python 缓存的整数范围是(-1, 100),不过这个范围是会改变的,所以请不要在你的应用程序使用这个特性。

Python 2.3 中决定,在预定义缓存字符串表之外的字符串,如果不再有任何引用指向它,那这个字符串将不会被缓存。也就是说, 被缓存的字符串将不会象以前那样永生不灭,对象回收器一样可以回收不再被使用的字符串。

4.5.3 布尔类型

布尔逻辑运算符 and, or 和 not 都是Python 关键字,这些运算符的优先级按从高到低的顺序列于表4.3. 

not 运算符拥有最高优先级,只比所有比较运算符低一级。 and 和 or 运算符则相应的再低一级。

运算符 功能

not expr expr 的逻辑非 (否)

expr1 and expr2 expr1 和 expr2 的逻辑与

expr1 or expr2 expr1 和 expr2 的逻辑或

>>> x, y = 3.1415926536, -1024
>>> x < 5.0
True
>>> not (x < 5.0) 
False
>>> (x < 5.0) or (y > 2.718281828) 
True
>>> (x < 5.0) and (y > 2.718281828)
 False
>>> not (x is y)
True
4.6 标准类型内建函数

Python 提供了一些内建函数用于这些基本对象类型:cmp(), repr(), str(), type(), 和等同于repr()函数的单反引号(``) 运算符。

表4.4 标准类型内建函数

函数 功能

  • cmp(obj1, obj2) 比较 obj1 和 obj2, 根据比较结果返回整数 i:

                i < 0 if obj1 < obj2

                i > 0 if obj1 > obj2

                i == 0 if obj1 == obj2

  • repr(obj) 或 `obj` 返回一个对象的字符串表示

  • str(obj) 返回对象适合可读性好的字符串表示

  • type(obj) 得到一个对象的类型,并返回相应的type 对象

4.6.1 type()

type() 的用法如下:

type(object)

type() 接受一个对象做为参数,并返回它的类型。它的返回值是一个类型对象

>>> type('Hello World!') # string type
<type 'string'>
>>>
>>> type(type(4)) # type type
<type 'type'>

注意type()有趣的输出。每个对象都可以实现一个可打印的字符串表示。不过并不总是这样, 对那些不容易显示的对象来说, Python 会以一个相对标准的格式表示这个对象,格式通常是这种形式: <object_something_or_another>, 以这种形式显示的对象通常会提供对象类别,对象id 或位置, 或者其它合适的信息。

4.6.2 cmp()

内建函数cmp()用于比较两个对象obj1 和obj2。比较是在对象之间进行的,不管是标准类型对象还是用户自定义对象。如果是用户自定义对象, cmp()会调用该类的特殊方法__cmp__()。在第13 章会详细介绍类的这些特殊方法。下面是几个使用cmp()内建函数的对数值和字符串对象进行比较的例子。

>>> a, b = -4, 12
>>> cmp(a,b)
-1
>>> cmp(b,a)
1
>>> b = -4
>>> cmp(a,b)
0
>>> a, b = 'abc', 'xyz'
>>> cmp(a,b)
-23
>>> cmp(b,a)
23
>>> b = 'abc'
>>> cmp(a,b)
0

在后面我们会研究cmp()用于其它对象的比较操作。

4.6.3 str()和 repr() (及 `` 运算符)

内建函数 str() 和 repr() 或反引号运算符(``) 可以方便的以字符串的方式获取对象的内容、类型、数值属性等信息。str()函数得到的字符串可读性好, 而repr()函数得到的字符串通常可以用来重新获得该对象, 通常情况下 obj == eval(repr(obj)) 这个等式是成立的。

这两个函数接受一个对象做为其参数, 返回适当的字符串:

>>> str(4.53-2j)
'(4.53-2j)'
>>>
>>> str(1)
'1'
>>>
>>> str(2e10)
'20000000000.0'
>>>
>>> str([0, 5, 9, 9])
'[0, 5, 9, 9]'
>>>
>>> repr([0, 5, 9, 9])
'[0, 5, 9, 9]'
>>>
>>> `[0, 5, 9, 9]`
'[0, 5, 9, 9]'

尽管str(),repr()和``运算在特性和功能方面都非常相似, 事实上 repr() 和 `` 做的是完全一样的事情,它们返回的是一个对象的“官方”字符串表示, 也就是说绝大多数情况下可以通过求值运算(使用eval()内建函数)重新得到该对象,但str()则有所不同。str() 致力于生成一个对象的可读性好的字符串表示,它的返回结果通常无法用于eval()求值, 但很适合用于 print 语句输出。需要再次提醒一下的是, 并不是所有repr()返回的字符串都能够用eval()内建函数得到原来的对象:

>>> eval(`type(type))`)
File "<stdin>", line 1
eval(`type(type))`)
^
SyntaxError: invalid syntax

也就是说 repr() 输出对 Python 比较友好, 而str()的输出对人比较友好。虽然如此,很多情况下这三者的输出仍然都是完全一样的。

核心笔记:为什么我们有了repr()还需要``?

在Python 学习过程中,你偶尔会遇到某个运算符和某个函数是做同样一件事情。之所以如此是因为某些场合函数会比运算符更适合使用。举个例子, 当处理类似函数这样的可执行对象或根据不同的数据项调用不同的函数处理时,函数就比运算符用起来方便。另一个例子就是双星号(**)乘方运算和pow()内建函数,x ** y 和 pow(x,y) 执行的都是x 的y 次方。译者注:事实上Python 社区目前已经不鼓励继续使用``运算符。

4.6.4 type() 和 isinstance()

Python 不支持方法或函数重载, 因此你必须自己保证调用的就是你想要的函数或对象。。幸运的是, 我们前面4.3.1 小节提到的type()内建函数可以帮助你确认这一点。一个名字里究竟保存的是什么?相当多,尤其是这是一个类型的名字时。

为了达到此目的,Python 提供了一个内建函数type()。来看几个使用type()内建函数返回多种对象类型的例子:

>>> type('')
<type 'str'>
>>> s = 'xyz'
>>> type(s)
<type 'str'>
>>> type(100)
<type 'int'>
>>> type(0+0j)
<type 'complex'>
>>> type(0L)
<type 'long'>
>>> type(0.0)
<type 'float'>
>>> type([])
<type 'list'>
>>> type(())
<type 'tuple'>
>>> type({})
<type 'dict'>
>>> type(type)
<type 'type'>
>>> class Foo: pass # new-style class
...
>>> foo = Foo()
>>> class Bar(object): pass # new-style class
...
>>> bar = Bar()
>>> type(Foo)
<type 'classobj'>
>>> type(foo)
<type 'instance'>
>>> type(Bar)
<type 'type'>
>>> type(bar)
<class '__main__.Bar'>

>>> type(Bar)

<type 'type'>

>>> type(bar)

<class '__main__.Bar'>【不知道这个地方是不是树上写错了?还是因为Bar继承了object所以结果有些不一样】

除了内建函数type(), 还有一个有用的内建函数叫 isinstance(). 我们会在第13 章(面向对象编程)正式研究这个函数,不过在这里我们还是要简要介绍一下你如何利用它来确认一个对象的类型。

在例 4.1 演示在运行时环境使用isinstance() 和 type()函数。随后我们讨论type()的使用以及怎么将这个例子移植为改用 isinstance()。

例4.1 检查类型(typechk.py)

def displayNumType(num):
    print (num, 'is',),
    if isinstance(num, (int, float, complex)):
        print ('a number of type:', type(num).__name__)
    else:
        print ('not a number at all!!')
displayNumType(-69)
displayNumType(9999999999999999999999)
displayNumType(98.6)
displayNumType(-5.2+1.9j)
displayNumType('xxx')

运行 typechk.py, 我们会得到以下输出:

-69 is
a number of type: int
9999999999999999999999 is
a number of type: int
98.6 is
a number of type: float
(-5.2+1.9j) is
a number of type: complex
xxx is
not a number at all!!

例子进阶

这个完成同样功能的函数与本书的第一版中的例子已经大不相同:

def displayNumType(num):
    print num, "is",
    if type(num) == type(0):
        print 'an integer'
    elif type(num) == type(0L):
        print 'a long'
    elif type(num) == type(0.0):
        print 'a float'
    elif type(num) == type(0+0j):
        print 'a complex number'
    else:
        print 'not a number at all!!'

由于Python 奉行简单但是比较慢的方式,所以我们必须这么做。会看到我们调用了两次 type()。要知道每次调用函数都会付出性能代价, 如果我们能减少函数的调用次数, 就会提高程序的性能。

利用在本章我们前面提到的 types 模块, 我们还有另一种比较对象类型的方法,那就是将检测得到的类型与一个已知类型进行比较:

>>> import types
>>> if type(num) == types.IntType...

对象值比较 VS 对象身份比较

我们已经讨论了对象的值比较和身份比较, 你会发现我们的代码在性能上还不是最优的.在运行时期,只有一个类型对象来表示整数类型.

也就是说,type(0),type(42),type(-100) 都是同一个对象: <type 'int'>(types.IntType 也是这个对象)

如果它们是同一个对象, 我们为什么还要浪费时间去获得并比较它们的值呢? 所以比较对象本身是一个更好地方案.下面是改进后的代码:

if type(num) is types.IntType... # or type(0)

这样做有意义吗? 我们用对象身份的比较来替代对象值的比较。如果对象是不同的,那意味着原来的变量一定是不同类型的。(因为每一个类型只有一个类型对象),我们就没有必要去检查(值)了。 一次这样的调用可能无关紧要,不过当很多类似的代码遍布在你的应用程序中的时候,就有影响了。

减少查询次数

为了得到整数的对象类型,解释器不得不首先查找types 这个模块的名字,然后在该模块的字典中查找IntType。通过使用from-import,你可以减少一次查询:

from types import IntType
if type(num) is IntType...

惯例和代码风格

Python2.2 对类型和类的统一导致 isinstance()内建函数的使用率大大增加。我们将在第13 章(面向对象编程)正式介绍isinstance(),在这里我们简单浏览一下。

这个布尔函数接受一个或多个对象做为其参数,由于类型和类现在都是一回事, int 现在既是一个类型又是一个类。

if isinstance(num, int)...

在判断对象类型时也使用 isinstance() 已经被广为接受, 我们上面的 typechk.py 脚本最终与改成了使用 isinstance() 函数。值得一提的是, isinstance()接受一个类型对象的元组做为参数, 这样我们就不必像使用type()时那样写一堆 if-elif-else 判断了。

4.6.5 Python 类型运算符和内建函数总结

表4.5 列出了所有运算符和内建函数,其中运算符顺序是按优先级从高到低排列的。同一种灰度的运算符拥有同样的优先级。注意在operator 模块中有这些(和绝大多数Python)运算符相应的同功能的函数可供使用。

134750_SJ58_724288.png

4.7 类型工厂函数

所有的内建类型现在也都是类, 在这基础之上, 原来的所谓内建转换函数象int(), type(), list() 等等, 现在都成了工厂函数。 也就是说虽然他们看上去有点象函数, 实质上他们是类。当你调用它们时, 实际上是生成了该类型的一个实例, 就象工厂生产货物一样。

下面这些大家熟悉的工厂函数在老的Python 版里被称为内建函数:

? int(), long(), float(), complex()

? str(), unicode(), basestring()

? list(), tuple()

? type()

以前没有工厂函数的其他类型,现在也都有了工厂函数。除此之外,那些支持新风格的类的全新的数据类型,也添加了相应的工厂函数。下面列出了这些工厂函数:

? dict()

? bool()

? set(), frozenset()

? object()

? classmethod()

? staticmethod()

? super()

? property()

? file()

4.8 标准类型的分类

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

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

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

? “数据”,因为他们用于一般数据存储

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

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

? “类型”,因为他们就是数据类型

事实上, 几个类型共享某一些的特性,比如功能的实现手段, 另一些类型则在访问数据值方面有一些共同之处。我们感兴趣的还有这些类型的数据如何更新以及它们能提供什么样的存储。

有三种不同的模型可以帮助我们对基本类型进行分类,每种模型都展示给我们这些类型之间的相互关系。

4.8.1 存储模型

我们对类型进行分类的第一种方式, 就是看看这种类型的对象能保存多少个对象。Python的类型,能容纳一个或多个值。

  • 一个能保存单个字面对象的类型我们称它为原子或标量存储;

  • 可容纳多个对象的类型,我们称之为容器存储。(容器对象有时被称为复合对象,不过这些对象并不仅仅指类型,还包括类似类实例这样的对象)容器类型又带来一个新问题,那就是它是否可以容纳不同类型的对象。所有的Python 容器对象都能够容纳不同类型的对象。

表4.6 按存储模型对Python 的类型进行了分类。

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

194040_WHmV_724288.png

4.8.2 更新模型

另一种对标准类型进行分类的方式就是, “对象创建成功之后,它的值可以进行更新吗?” 可变对象允许他们的值被更新,而不可变对象则不允许他们的值被更改。

表4.7 列出了支持更新和不支持更新的类型。哎,“等等,你说数值和字符串对象是不可改变的?看看下面的例子!”:

x = 'Python numbers and strings'
x = 'are immutable?!? What gives?'
i = 0
i = i + 1

“在我看来, 这可不象是不可变对象的行为!” 事实上是一个新对象被创建,然后它取代了旧对象。就是这样。

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

194252_g60Y_724288.png

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

>>> x = 'Python numbers and strings'
>>> print id(x)
16191392
>>> x = 'are immutable?!? What gives?'
>>> print id(x)
16191232
>>> i = 0
>>> print id(i)
7749552
>>> i = i + 1
>>> print id(i)
7749600

这个数字与该对象当时分配的内存地址密切相关。因此不同的机器, 不同的执行时间都会生成不同的对象身份。另一类对象, 列表可以被修改而无须替换原始对象, eg:

>>> aList = ['ammonia', 83, 85, 'lady']
>>> aList
['ammonia', 83, 85, 'lady']
>>> aList[2]
85
>>> id(aList)
135443480
>>> aList[2] = aList[2] + 1
>>> aList[3] = 'stereo'
>>> aList
['ammonia', 83, 86, 'stereo']
>>> id(aList)
135443480
>>> aList.append('gaudy')
>>> aList.append(aList[2] + 1)
>>> aList
['ammonia', 83, 86, 'stereo', 'gaudy', 87]
>>> id(aList)
135443480

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

4.8.3 访问模型

根据访问我们存储的数据的方式对数据类型进行分类。在访问模型中共有三种访问方式:直接存取,顺序,和映射。对非容器类型可以直接访问。所有的数值类型都归到这一类。

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

(2)映射类型类似序列的索引属性,不过它的索引并不使用顺序的数字偏移量取值, 它的元素无序存放, 通过一个唯一的key 来访问, 这就是映射类型, 它容纳的是哈希键-值对的集合。我们在以后的章节中将主要使用访问模型,详细介绍各种访问模型的类型, 以及某个分类的类型之间有哪些相同之处(比如运算符和内建函数), 然后讨论每种Python 标准类型。所有类型的特殊运算符,内建函数, 及方法都会在相应的章节特别说明。

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

194929_l9PT_724288.png

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

如下表中列出了所有的标准类型, 我们使用的三个模型,以及每种类型归入的分类。

表4.9 标准类型分类

194913_5p9v_724288.png

4.9 不支持的类型

在我们深入了解各个标准类型之前,我们在本章的结束列出Python 目前还不支持的数据类型。

char 或 byte

Python 没有 char 或 byte 类型来保存单一字符或8 比特整数。你可以使用长度为1 的字符串表示字符或8 比特整数。

指针

Python 替你管理内存,因此没有必要访问指针。在Python 中你可以使用id()函数得到一个对象的身份号, 这是最接近于指针的地址。因为你不能控制这个值,所以其实没有太大意义。其实在Python 中, 一切都是指针。

int vs short vs long

Python 的普通整数相当于标准整数类型,不需要类似C 语言中的 int, short, long 这三种整数类型。事实上Python 的整数实现等同于C 语言的长整数。 由于Python 的整型与长整型密切融合, 用户几乎不需要担心什么。 你仅需要使用一种类型, 就是Python 的整型。即便数值超出整型的表达范围, 比如两个很大的数相乘, Python 会自动的返回一个长整数给你而不会报错。

float VS double

C 语言有单精度和双精度两种浮点类型。 Python 的浮点类型实际上是C 语言的双精度浮点类型。 Python 认为同时支持两种浮点类型的好处与支持两种浮点类型带来的开销不成比例,所以Python 决定不支持单精度浮点数。对那些宁愿放弃更大的取值范围而需要更高精确度的用户来说, Python 还有一种十进制浮点数类型 Decimal, 不过你必须导入decimal 模块才可以使用它。浮点数总是不精确的。Decimals 则拥有任意的精度。在处理金钱这类确定的值时,Decimal 类型就很有用。 在处理重量,长度或其它度量单位的场合, float 足够用了。


转载于:https://my.oschina.net/cqlcql/blog/656363

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值