?? Python 对象
?? 内建类型
?? 标准类型运算符
?? 值的比较
?? 对象身份比较
?? 布尔类型
?? 标准类型内建函数
?? 标准类型总览
?? 各种类型
?? 不支持的类型
4.1 Python 对象
Python 使用对象模型来存储数据。
构造任何类型的值都是一个对象。
尽管Python 通常当成一种“面向对象的编程语言”,但你完全能够写出不使用任何类和实例的实用脚本。
不过Python 的对象语法和架构鼓励我们使用这些特性,下面让我们仔细研究一下Python 对象。
所有的Python 对像都拥有三个特性:身份,类型和值。
身份
每一个对象都有一个唯一的身份标识自己,任何对象的身份可以使用内建函数id()来得到。
这个值可以被认为是该对象的内存地址。
您极少会用到这个值,也不用太关心它究竟是什么。
类型
对象的类型决定了该对象可以保存什么类型的值,可以进行什么样的操作,以及遵循什么样的规则。
您可以用内建函数type()查看Python 对象的类型。
因为在Python 中类型也是对象(还记得我们提到Python 是面向对象的这句话吗?),所以type()返回的是对象而不是简单的字符串。
值
对象表示的数据项
上面三个特性在对象创建的时候就被赋值,除了值之外,其它两个特性都是只读的。
如果对象支持更新操作,那么它的值就可以改变,否则它的值也是只读的。
对象的值是否可以更改被称为对象的可改变性(mutability).
4.2 标准类型
?? 数字(分为几个子类型,其中有三个是整型)
?? 整型
?? 布尔型
?? 长整型
?? 浮点型
?? 复数型
?? 字符串
?? 列表
?? 元组
?? 字典
4.3 其他内建类型
?? 类型 [所有类型对象的类型都是type,它也是所有Python 类型的根和所有Python 标准类的默认元类(metaclass)]
?? Null 对象 (None) [空对象、值为零的任何数字或者Null 对象 None 的布尔值都是False.]
?? 文件
?? 集合/固定集合
?? 函数/方法
?? 模块
?? 类
类型对象和type 类型对象
对象具有一系列固有行为和特性(比如支持哪些运算,具 有哪些方法)必须事先定义好。
从这个角度看,类型正是保存这些信息的最佳位置。
描述一种类型所需要的信息不可能用一个字符串来搞定,所以类型不能是一个简单的字符串,这些信息不能也不应该和数据保存在一起, 所以我们将类型定义成对象。
>>> type(42)
<type ‘int‘>
>>> type(type(42))
<type ‘type‘>
>>>
None, Python 的 Null 对象
Python 有一个特殊的类型,被称作 Null 对象或者 NoneType,它只有一个值,那就是 None。
>>> type(None)
<type ‘NoneType‘>
>>>
它不支持任何运算也没有任何内建方法。
如果非常熟悉C 语言,和None 类型最接近的C 类型就 是void,None 类型的值和C 的NULL 值非常相似(其他类似的对象和值包括Perl 的undef 和Java 的void 类型与null 值)。
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 源代码片段,它是可执行对象。
通过调用内建函数compile() 可以得到代码对象。
代码对象可以被 exec 命令或 eval()内建函数来执行。
代码对象本身不包含任何执行环境信息, 它是用户自定义函数的核心, 在被执行时动态 获得上下文。
(事实上代码对象是函数的一个属性)一个函数除了有代码对象属性以外,还有一些其它函数必须的属性,包括函数名,文档字符串,默认参数,及全局命名空间等等。
帧对象
帧对象表示 Python 的执行栈帧。
帧对象包含Python 解释器在运行时所需要知道的所有信 息。
它的属性包括指向上一帧的链接,正在被执行的代码对象(参见上文),本地及全局名字空间字典以及当前指令等。
每次函数调用产生一个新的帧,每一个帧对象都会相应创建一个C 栈帧。
用到帧对象的一个地方是跟踪记录对象。
跟踪记录 对象
当异常发生时,一个包含针对异常的栈跟踪信息的跟踪记录对象被创建。
如果一个异常有 自己的处理程序,处理程序就可以访问这个跟踪记录对象。
切片对象
当使用Python 扩展的切片语法时,就会创建切片对象。
扩展的切片语法允许对不同的索引 切片操作,包括步进切片, 多维切片,及省略切片。
多维切片语法是 sequence[start1 : end1, start2 : end2], 或使用省略号, sequence[...,start1 : end1 ]。
切片对象也可以由内建函数 slice()来生成。
步进切片允许利用第三个切片元素进行步进切片,它的语法为sequence[起始索引 : 结束索引 : 步进值]。
Python 很早就支持扩展步进切片语法了,但直到Python2.3 以前都必须依靠 C API 或 Jython 才能工作。
>>> foostr = ‘abcde‘
>>> foostr[::-1]
‘edcba‘
>>> foostr[::-2]
‘eca‘
>>> foolist = [123, ‘xba‘, 342.23, ‘abc‘]
>>> foolist[::-1]
[‘abc‘, 342.23, ‘xba‘, 123]
省略对象
省略对象用于扩展切片语法中,起记号作用。 这个对象在切片语法中表示省略号。
类似 Null 对象 None, 省略对象有一个唯一的名字 Ellipsis, 它的布尔值始终为 True.
XRange 对象
调用内建函数 xrange() 会生成一个Xrange 对象,xrange()是内建函数 range()的兄弟版 本, 用于需要节省内存使用或 range()无法完成的超大数据集场合。
4.5 标准类型运算符
对象值的比较
比较运算符用来判断同类型对象是否相等,所有的内建类型均支持比较运算,比较运算返回布尔值 True 或 False。
注意,实际进行的比较运算因类型而异。
换言之,数字类型根据数值的大小和符号比较,字符串按照字符序列值进行比较,等等。
>>> 2 == 2
True
>>> 2.46 <= 8.33
True
>>> 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
比较操作是针对对象的值进行的,也就是说比较的是对象的数值而不是对象本身。
标准类型值比较运算符
运算符 功能
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 风格)
注: 未来很有可能不再支持 <> 运算符,建议您一直使用 != 运算符。
对象身份比较 (比较对象本身是一个更好地方案)
作为对值比较的补充,Python 也支持对象本身的比较。
对象可以被赋值到另一个变量(通 过引用)。
因为每个变量都指向同一个(共享的)数据对象,只要任何一个引用发生改变,该对象的其它引用也会随之改变。
例1: foo1 和 foo2 指向相同的对象
foo1 = foo2 = 4.3
当你从值的观点看这条语句时, 它表现的只是一个多重赋值,将4.3 这个值赋给了foo1 和foo2 这两个变量。
这当然是对的, 不过它还有另一层含义。 事实是一个值为4.3 的数字对象被创建,然后这个对象的引用被赋值给foo1 和foo2, 结果就是 foo1 和 foo2 指向同一个对象。
图4–1 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. 然后第二个数值对象被 创建并赋值给foo2。
尽管两个对象保存的是同样大小的值,但事实上系统中保存的都是两个独立的对象,其中foo1 是第一个对象的引用, foo2 则是第二个对象的引用。
图4–2 foo1 和 foo2 指向不同的对象
Python 提供了is 和is not运算符来测试两个变量是否指向同一个对象。
a is b
这个表达式等价于下面的表达式
id(a) == id(b)
is 与 not 标识符都是 Python 关键字。
核心提示:实践
整数对象和 字符串对象是不可变对象,所以Python 会很高效的缓存它们。
这会造成我们认为Python 应该创建新对象时,它却没有创建新对象的假象。
Python 仅缓存简单整数,因为它认为在Python 应用程序中这些小整数会经常被用到。
Python 2.3 中决定,在预定义缓存字符串表之外的字符串,如果不再有任何引用指向它, 那这个字符串将不会被缓存。
也就是说, 被缓存的字符串将不会象以前那样永生不灭,对象回收器一样可以回收不再被使用的字符串。
从Python 1.5 起提供的用于缓存字符的内建函数intern() 也已经不再推荐使用, 即将被废弃。
布尔类型
布尔逻辑运算符 and, or 和 not 都是Python 关键字。
not 运算符拥有最高优先级,只比所有比较运算符低一级, and 和 or 运算符则相应的再低一级。
Python 支持一个表达式进行多种比较操作, 其实这个表达式本质上是由 多个隐式的 and 连接起来的多个表达式。
>>> 3 < 4 < 7 # same as "( 3 < 4 ) and ( 4 < 7 )"
True
4.6 标准类型内建函数
函数 功能
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 对象
type()
type() 的用法如下:
type(object)
type() 接受一个对象做为参数,并返回它的类型。它的返回值是一个类型对象。
>>> type(4) # int type
<type ‘int‘>
>>>
>>> type(‘Hello World!‘) # string type
<type ‘string‘>
>>>
>>> type(type(4)) # type type
<type ‘type‘>
>>> class Foo:pass
...
>>> class Bar(object):pass
...
>>> type(Foo)
<type ‘classobj‘>
>>> type(Bar)
<type ‘type‘>
>>> type(Foo() )
<type ‘instance‘>
>>> type(Bar())
<class ‘__main__.Bar‘>
>>>
cmp()
内建函数cmp()用于比较两个对象obj1 和obj2, 如果obj1 小于obj2, 则返回一个负整 数,如果obj1 大于obj2 则返回一个正整数, 如果obj1 等于obj2, 则返回0。
它的行为非常类似于C 语言的strcmp()函数。
比较是在对象之间进行的,不管是标准类型对象还是用户自定义对象。
如果是用户自定义对象, cmp()会调用该类的特殊方法__cmp__()。
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 社区目前已经不鼓励继续使用``运算符。
type() 和 isinstance()
Python 不支持方法或函数重载, 因此你必须自己保证调用的就是你想要的函数或对象。
type()返回任意Python 对象对象的类型,而不局限于标准类型。
Python2.2 统一了类型和类.
>>> 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‘>
使用 isinstance() 函数
Python 类型运算符和内建函数总结
表4.5 列出了所有运算符和内建函数,其中运算符顺序是按优先级从高到低排列的。
同一 种灰度的运算符拥有同样的优先级。
注意在operator 模块中有这些(和绝大多数Python)运算符相应的同功能的函数可供使用。
4.7 类型工厂函数
Python 2.2 统一了类型和类, 所有的内建类型现在也都是类, 在这基础之上, 原来的 所谓内建转换函数象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 的类型, 就象绝大多数其它语言一样,能容纳一个或多个值。
一个能保存单个字面对象的类型我们称它为原子或标量存储,那些可容纳多个对象的类型,我们称之为容器存储。
容器对象有时会在文档中被称为复合对象,不过这些对象并不仅仅指类型,还包括类似类实例这样的对象。
容器类型又带来一个新问题,那就是它是否可以容纳不同类型的对象。
所有的Python 容器对 象都能够容纳不同类型的对象。
字符串看上去像一个容器类型,因为它“包含”字符(并且经常多于一个字符),不过由于Python 并没有字符类型(参见章节4.8),所以字符串是一个自我包含的文字类型。
表4.6 以存储模型为标准的类型分类
存储模型
分类 Python 类型
标量/原子类型 数值(所有的数值类型),字符串(全部是文字)
容器类型 列表、元组、字典
更新模型
另一种对标准类型进行分类的方式就是, 针对每一个类型问一个问题:“对象创建成功之 后,它的值可以进行更新吗?”
某些类型允许他们的值进行更新,而另一些则不允许。
可变对象允许他们的值被更新,而不可变对象则不允许他们的值被更改。
新创建的对象被关联到原来的变量名, 旧对象被丢弃,垃圾回收器会在适当的时机回收这 些对象。
你可以通过内建函数id()来确认对象的身份在两次赋值前后发生了变化。
表4.7 以更新模型为标准的类型分类
更新模型
分类 Python 类型
可变类型 列表, 字典
不可变类型 数字、字符串、元组
访问模型
尽管前面两种模型分类方式在介绍Python 时都很有用,它们还不是区分数据类型的首要模型。
对这种目的,我们使用访问模型。也就是说根据访问我们存储的数据的方式对数据类型进行分类。
在访问模型中共有三种访问方式:直接存取,顺序,和映射。
对非容器类型可以直接访问。
所有的数值类型都归到这一类。
序列类型是指容器内的元素按从0 开始的索引顺序访问。
一次可以访问一个元素或多个元 素, 也就是大家所了解的切片(slice)。
字符串, 列表和元组都归到这一类。
映射类型类似序列的索引属性,不过它的索引并不使用顺序的数字偏移量取值, 它的元素 无序存放, 通过一个唯一的key 来访问, 这就是映射类型, 它容纳的是哈希键-值对的集合。
表4.7 以访问模型为标准的类型分类
访问模型
分类 Python 类型
直接访问 数字
顺序访问 字符串、列表、元组
映射访问 字典
表4.9 标准类型分类
数据类型 存储模型 更新模型 访问模型
数字 Scalar 不可更改 直接访问
字符串 Scalar 不可更改 顺序访问
列表 Container 可更改 顺序访问
元组 Container 不可更改 顺序访问
字典 Container 可更改 映射访问
4.9 不支持的类型
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 足够用了。