数值类型
本章我们将镜像更深入的Python语言之旅。在Python中数据采用了对象的形式----无论是Python所提供的内置对象,或是我们使用Python工具以及像C这样的其他语言所创建的对象。事实上,对象是一切Python程序的基础,因为对象是Python程序中的最基本的概念,所以对象也是本书第一个关注的焦点。
在上一章,我们对Python的核心对象类型进行了概览。尽管上一章已经介绍了最核心的术语,但受限于篇幅,并没有涉及更多的细节。本章将对数据类型进行更详尽的学习,以此步长之前略过的细节。
数值类型基础知识
Python中大部分数值类型都是相当典型的。它们可以用来记录你的银行收支,地球到火星的距离,访问网站的人数以及其他任何数值量。
在Python中,组织并不真的只是一种对象类型,而是一组类似的分类、Python不仅支持通常的数字类型,还提供了字面量来直接创建数字和表达式以处理数字,此外,Python为更高级的工作提供了很多高级数值编程支持和对象。完整的Python数值类型包括:
整数和浮点数
复数对象
小数:固定精度对象
分数:有理数对象
集合:带有数值运算的集合体
布尔值:真和假
内置函数和模块:round,math,random等
表达式;无限制整数精度;位运算;十六进制,八进制和二进制
第三方扩展:向量,库,可视化,作图等
数值字面量
Python提供了整数以及浮点数作为基本类型。Python还允许我们使用十六进制,八进制和二进制怎么量来表示整数;提供了一个复数类型,并且允许整数具有无限的精度----只要内存空间允许,整数可以增长任意位数的数字。
字面量 解释
1234,-456,0,9999999999 整数,无大小限制
1.23,1.,3.14e-10,4E210,4.0e+210 浮点数
0o177,0x9ff,0b101010 Python3.X中的八进制,十六进制和二进制
3+4j,3.0+4.0j,3J 复数
set('sadw'),{1,2,3,4} 集合
Decimal('1.0),Fraction(1,3) 小数和分数扩展类型
bool(X),True,False 布尔类型
一些需要强调的:
整数和浮点数字面量:整数写成十进制数字的长。浮点数带一个小数点,也可以加上一个科学技术标志e或者E。
Python3.X中的整数:一般整数和长整数已经合二为一了。
十六进制数、八进制和二进制字面量:整数可以编写为十进制,十六进制,八进制,二进制形式、后三者在一些编程领域是常见的。十六进制以0X或0X开头,后面接十六进制的数字0-9和A-F。十六进制的数字编写成大写或小写都可以。八进制数字字面量以0o或0O开头,后面节着数字0-7组成的数字串。要注意!所有这些字面量在程序代码中都产生一个整数对象,它们仅仅是特定值的不同语法表示而已。
复数:Python的复数字面量写成实部+虚部的写法,这里虚部都是以J或者j结尾。其中,实部从技术上讲可以省略,所以虚部可以独立于实部存在。从内部来看,复数是通过一对浮点数来实现的,但是对复数的所有数字运算都会按照复数的运算法则进行。
编写其他的数值类型:你需要导入某些模块并调用其函数来创建一些数值类型(如小数和分数),其他的一些拥有它们自己的字面量语法(如集合)。
内置数值工具
Python提供了一系列处理数字对象的工具:
表达式运算符 +、-、*、/、>>、**、&等
内置数学函数pow、abs、round、int、hex、bin、等
工具模块 random、math等
随着深入的学习这些我们都会见到。
尽管数字主要还是通过表达式,内置函数和模块处理,但是它们如今也拥有许多专门用于类型的方法。这些内容将在本章中介绍。例如,浮点数拥有一个as_integer_ratio方法,它对分数数值类型很有用。
Python表达式运算符
表达式是处理数字的最基本工具,表达式的定义是:数字(或其他对象)与运算符相结合,并被Python在执行时计算为一个值。在Python中你可以使用一般的数学记号与运算符号来编写表达式。例如,让两个数字X和Y相加就可以写成X+Y
下面列举我所知Python中所有的运算符表达式
这个是表5-1
运算符 描述
yield x 生成器函数send协议
lambda args:expression 创建匿名函数
x if y else z 三元选择表达式
x or y 逻辑或
x and y 逻辑与
not x 逻辑非
x in y,x not in y 成员关系
x is y,x is not y 对象同一性测试
xy,x>=y 大小比较,集合的子集和超集
x == y,x != y 值等价性运算符
x|y 按位或,集合合并
x^y 按位异或,集合对称差集
x&y 按位与,集合交换
x<>y 将x左移或右移y位
x + y 加法,拼接
x - y 减法,集合差集
x*y 乘法,重复
x%y 求余数,格式化
x / y,x//y 真除法,向下取整除法
-x,+x 取负,取正
~x 按位非(取反码)
x ** y 幂运算(指数)
x[i] 索引(映射,序列等)
x[i:j:k] 分片,切片
x(...) 调用(函数,类,方法,其他可调用对象)
x.attr 属性引用
(....) 元组,表达式,生成器表达式
[...] 列表,列表推导
{...} 字典,集合,集合与字典推导
书上即介绍2.X又涉及3.X。因此这里做出一些讲解。
Python3.X中x <> y的格式被移除了。
Python3.X中,X/Y表达式会执行真除法(保留了商的小数部分)
[...]语法同时用于列表字面量,列表推导表达式。列表推导表达式会执行隐式的循环并把表达式的结果收集到一个新列表中。
(...)语法用于元组和表达式分组,以及生成器表达式。生成器是一种能够按照需求产出结果的列表推导,而不是一次性创建完整的结果列表。
{...}语法用于字典字面量,并且在Python3.X和2.X可以表示集合字面量以及字典和集合推导。
yield和三元if/else选择表达式在Python2.5以及之后的版本可用。yeild会在生成器中返回send(...)参数;三元if/esle选择表达式是一个多行if语句的简化形式。如果yield不是单独位于第一条赋值语句的邮编,需要用圆括号
比较运算符可用链式使用:x
分片表达式X[I:J:K等同于用一个分片对象进行索引
在Python3.X中,比较非数字的混合类型的相对大小是不允许的。
在Python3.X中,字典的相对大小比较也不再支持(尽管支持等价性测试):比较sorted(aDict.items())是一种可能的替代
混合运算遵循运算符优先级
对于复杂的表达式,例如 A *B + C * D 。Python是怎么知道先进行哪个操作呢?这个问题的答案就在于对运算符优先级。当编写含有一个运算符以上的表达式时,Python将按照所谓的优先级法则对表达式进行分组,这个分组决定了表达式中各部分的计算顺序,
在表5-1中,运算符从上到下优先级逐渐增高,也就是说越下面的表达式‘绑定的越紧’
表5-2中位于同一行的表达式在分组的时候通常按照从左到右组合(除了幂运算,它是从右向左组合的,还有比较运算,是从左到右连接的)
如计算X + Y * Z 。Python首先计算乘法Y * Z,然后键结果与X相加
括号分组子表达式
如果用圆括号将表达式各部分进行分组,就可以不必记忆优先级的事情了,当你但子表达式括在圆括号中时,就会超越Python的优先级规则。Python总会先计算圆括号中的表达式么然后将结果用于整个表达式中。
例如,表达式X +Y * Z 写成下边两个表达式中的任意一种,从而强制Python按照你想要的顺序计算表达式:
(X + Y) * Z X + (Y * Z)
在第一种情况下,“+”首先用作与X和Y。在第二种情况下,首先使用“*”(即使这里没有括号也会这样)。一般来说,在一个大型表达式中添加括号是个很好的做法,因为它不仅能够按照你想要的循序进行计算,也增加了程序的可读性。
混合类型向上转换
除了在表达式中混个运算符外,你也可以混合数值类型。例如把一个整数和浮点数相加:
40 + 3.14
但这将会引发一个问题,混合后的结果是什么类型?
在混合类型的表达式中,Python实现将被操作的对象转换为最复杂的操作数的类型,然后在对相同类型的操作数进行数学运算。
Python是这样划分数值类型的复杂度的:整数比浮点数简单,浮点数比复数简单。因此当一个整数与浮点数混合时,整数会先升级为浮点数的值,之后通过浮点数的有运算法则得到浮点数的结果。
同理,任意混合类型的表达式,其中一个操作数是更为复杂的数字,就会导致其他的凑整数升级为一个复制的数字,使表达式获得一个复杂的结果。
你可以通过手动调用内置函数来强制转换类型
然而,你通常不需要这样做,因为Python在表达式中会自动升级为更复杂的类型,其结果往往就是你想要的。
再者,要记住所有这些混合类型的转换仅适用于数值类型。一般来说,Python不会在其他的类型之间进行转换。例如,一个字符串和一个整数相加就会产生错误。
预习:运算符重载和多态
尽管我们目前把注意力集中在内置数字上,但是所有的Python运算符都可以被Python的类或C扩展类型重载(即实现),从而能用于你自己创建的对象。例如,你之后将看到用类编写的对象代码也可以使用x+y表达式做加法或者拼接,以及使用x[i]表达式进行索引等。
再者,Python本书也自动重载了某些运算符,这样它能够根据所处理的内置对象的类型执行不同的操作。例如,“+”运算符是用于数字做加法,而用于字符串或列表这样的序列对象是做拼接运算。实际上,“+"用在你自己定义的类的对象上时,可以进行你想要的任意操作。
如前一章所述,这种特性通常称为多态。多态指的是:操作的意义由操作对象来决定。我们将会在第16章重访并深入探索这个概念,因为在那里的上下文中,可以看出多态的更多明显的特征。
数字的实际应用
让我们向代码金发!也许理解数值对象和表达式最好的方式,就是看看它们在实际中的应用。因此在掌握基础知识后,让我们打开交互命令行,来实验一些简单的说明性运算。
变量与基础表达式
首先,让我们练习一下基本的数学运算。在下面的交互中,首先把两个变量赋值为整数,以便在更大的表达式中使用他们。变量就是每次,可以用于记录程序中的信息。
变量在第一次赋值时被创建。
变量在表达式中使用时,会替换成它们的值。
变量在表达式中使用之前,编写已被赋值。
变量引用对象,而且从不需要事先说明。
换句话说,喜爱喵喵赋值语句会自动创建变量a和b
这里要插入性的讲一个东西:注释。注释是为代码编写人类可读的文档的方式,也属于编程中一个重要的部分。简单来说,就是记录一些话语在代码中,但不会改变代码,注释不会被执行。可以帮助你更好的理解代码,如何帮助取决于如何写注释。注释不是程序,就是一些话,随便说,随便唠。这里先说一个最简单的,#,在井号后面的语句不会被执行。
由于现在还处于交互命令行模式下,井号这种单行注释足够我们现在使用了。以后会讲解一个多行字符串。还有一点,网上有些段子讲注释的,一般常见的是// ,要记住,这个//不是Python的注释,放这不好使。
回到正题,目前,a和b的值是3和4,当变量被用在表达式中,它们就会自动被替换成它们的值,而当我们在交互模式下运行时,表达式的结果会在计算完成后立即显示出来。
从技术上来讲,这里显示的结果是包含两个值的元组,因为在输入行上包含了两个逗号分隔的表达式,这就是为什么显示结果被包含在括号内的原因。要注意,表达式能够准确工作是因为a和b已经被提前赋值,如果选择一个还未赋值的变量
你不需要在Python中提前声明变量,但变量在使用之前必须至少被赋值一次,实际上,这意味着你在累加计算器之前必须先将它们赋值为0,在列表尾部添加元素之前必须先初始化一个空列表。
下面是两个稍长一些的表达式,它们展示了运算符分组以及类型转化。
在第一个表达式中,因为没有括号,所以Python会自动根据运算符有限级将各部分分组。要注意,在第一个表达式中所有的数字都是除法,但是得到的结果是5.0,。因为在Python3.X中“/”会执行真除法,如果想要得到5,应该使用//。
在第二个表达式中,括号用在“+”的周围,强制Python先计算+。并且通过增加小数点让其中应该操作对象是浮点数2.0.因此是混合类型,Python在进行+之前会先将a引用的整数变成浮点数的值。
数值的显示格式
这是对于一些早期版本的问题。
对于这个例子如果你的版本在2.7之前,Python3.0或更早的版本可能会看到不同的结果。
可能是0.8000000000000000000004。这个奇怪的结果背后真正的原因是浮点数的硬件限制,以及浮点数无法精确的表示一些值。因为计算机体系结构不在本书的讨论范围,所以我们只能说你的计算机的浮点数硬件已经尽力了,而硬件和Python在这里都没有问题。
实际上,这真的只是一个显示问题----交互式命令行下的自动结果显示会比这里的print语句显示更多的数字位数,是因为它使用了不同的算法。它们在内存中对应的是相同的数字。如果你不想看到所有的位数,那就使用print。从版本2.7和3.1起,Python的浮点数显示逻辑尝试变得更加智能,它经常能够显示较少的位数,但偶尔会更多。
str和repr显示格式
从技术上讲,默认的交互式命令行显示和print的区别,相当于内置repr和str函数的区别
str和repr都会把任意对象转换为对应的字符串表示:repr(以默认的交互式命令行显示)会产生看起来像代码的结果。str(和prin操作)转换为一种通常对用户更友好的格式。对象同时拥有这两种方式:str用于一般用途,repr带有额外的细节,我们在学完字符串以及类中的运算符重载后,会重新认识这一概念。
除了为任意对象提供可打印的字符串,str内置函数也是字符串数据类型的名称,而且在python3.X中可以传入一个编码名称,从一个字节串中解码一个Unicode字符串。
这也是第四章见过的bytes。decode方法的替代方案。
普通比较与链式比较
数字可以进行比较,一般的比较的能够像我们所期待的那用用于数字,它们会比较操作数的相对大小,并且返回一个布尔类型的结果,我们一般会对这个布尔类型进行测试,并以此决定在更大的语句和程序中接下来要执行的内容。
再次注意数值表达式中是如何允许混合类型,在上面第二个表达式中,Python比较了更为复杂的浮点类型的值。
Python还允许我们把多个比较链接起来执行范围测试、链式比较是更大的布尔表达式的简写,简而言之,Python允许将大小比较测试链接起来,形成如范围测试的连续比较。
获得false的结果是一样的,并且允许任意的链式长度
你可以在链式测试中使用其他的比较,不过最终的表达式可能会很晦涩,除非你使用Python的方式来计算。
Python不会把表达式1 == 2 的False的结果与3进行比较,这样做的话,从技术上的含义与0 < 3相同,即得到True(True和False只不过是定制化的1和0)
在继续学习之前在说一点,除了链接,数值的比较是基于相对大小的,这很容易理解。不过。浮点数不会总是按照你的预期工作,而且有时你要借助其他处理才能进行有意义的比较。
这是由于浮点数因为有限的比特位数,而不能精确地表示某些值的事实----这是数值编程的一个基本问题,而不只是在Python中出现,我们后面会学到小数和分数,它们是能够避免这些限制的工具。
除法:经典除法,向下取整除法和真除法
实际上,Python中有三种风格的除法,以及两种不同的除法运算符,其中一种运算符在Python3.X中有所变化。这部分内容非常细节化,但它是Python3.X中另一个主要的变化,而且可能会破坏2.X的代码,所以下面直接给出除法运算符的描述。
X / Y
经典除法和真除法。在Python2.X或者之前的版本中,这个操作对于整数会省去小数部分,对于浮点数会保持余项(小数部分)。在Python3.X变成真除法,即无论任何类型。最终的浮点数结果都会保留小数部分。
X // Y
向下取整除法。这是从Python2.2开始新增的操作,在Python2.X和3.X中均能使用,这个操作不考虑对象的类型,总是会省略结果的小数部分,剩下最小的能够整除的整数部分,它的结果类型取决于操作数的类型。
Python中引入真除法,是为了解决经典除法的结果依赖于操作数类型(这种结果在Python这样的动态语言中很难预料)的现象。由于这一限制,Python3.X中移除了经典除法: / 和 // 运算符在Python3.X中分别实现了真除法和向下取整除法。
在Python3.X中, / 现在总是执行真除法,不管操作数的类型,都返回包含任意小数部分的一个浮点数结果。// 执行向下取整除法,它截取掉余数并针对整数操作数返回一个整数,如果有一个操作数是浮点数类型,则返回一个浮点数。
要注意,在Python3.X中,//的结果的数据类型总是依赖于操作数的类型:如果操作数中有一个是浮点数,结果就是浮点数,否则,结果就是一个整数。
此外,由于// 运算符是作为依赖于整数截断除法的程序而引入的一种兼容性工具,因此它必须为整数返回整数。
向下取整除法 vs 截断除法
一个细节是: // 运算符有一个非正式的别名,叫做截断除法,不过更准确的说法应该是向下取整除法。 // 把结果向下截断到它的下层,即真正结果之下的最近的整数,其直接效果是向下舍入,并不是严格的截断,并且这对负数也有效,你可以使用Python的math模块来查看其中的区别。
floor返回数字的下舍整数,所以返回的结果的是-3
和 // 结果相同,这意味着 // 不是对结果去除小数部分,而是向下获取最近的整数。所以称为向下取整除法。
对于trunc是对结果进行截断操作,其结果总是趋近于0,。
对于正数,截断除法和向下取整除法是相同的,对于负数,//向下取整除法得到的是 向下 最近的最近整数。(向下!向下!向下!!!)
为什么截断很很重要
Python3.X中的 / 的非截断行为还是可能会影响到大量的Python2.X程序。可能是因为C语言的遗留原因,很多程序员仍然依赖于整数的截断除法,因此他们必须学习在这些场景下使用 //。今天,你应该在编写的所有新的Python2.X和3.X代码中这样做,在Python2.X中这样做是为了向Python3.X兼容,而在Python3.X中这样做是因为/在Python3.X中不再截断。
整数精度
除法可能在Python的各个版本中有所区别,但它仍然是相对标准的。下面是一些看起来比较奇怪的内容。如目前所述,Python3.X整数支持无限制的大小:
无限制精度整数是一个方便的内置工具。例如你可以直接在Python中以美分为单位来计算美国国家财政赤字。
由于Python需要为支持扩展精度而进行额外的工作,因此在实际应用中,长整型数的数学运算符通常要比支持的整数运算更慢。然而,如果你确实要求精度,更应该庆幸Python为你提供了内置的长整数支持,而不是抱怨其性能上的不足。
复数
尽管复数比我们之前介绍的这些类型要少见一些,我上次计算这东西还是高中,但Python中的复数是一种独特的对象类型,它们通常在工程和科学应用程序中使用。如果你知道复数是什么,你就会知道它的重要性,如果你不知道,那么就把这一步作为选读材料。(这是高中数学的内容,我只记得 j**2 == -1)
复数表示为两个浮点数(实部和虚部)并接在虚部增加了J或者j的后缀。我们也可以吧实部非零的复数写作实部与虚部相加的形式,并与 + 相连。例如,一个复数的实部为2,虚部为-3,可以写作 2 + -3j。下面是一些复数运算的例子。
复数允许我们将它的实部和虚部作为属性来访问,并支持所有一般的数学表达式,同时还可以通过标准的cmath模块中的工具进行处理。因为复数在绝大多数编程领域都比较罕见,因此这里我们将跳过复数的其他内容。参阅Python的语言参考手册来获取更多的细节。
十六进制、八进制和二进制:字面量与转换
Python的整数能够以十六进制、八进制和二进制计数法来编写,这作为我们迄今为止一种使用的常见的以10为底的十进制记数法的补充。对于十进制生物而言,其他这三种进制第一眼看起来充满异域风情,但是一些程序员会认为他们在描述一些数值时十分方便,尤其是他们能很容易地对应到字节和位。之前简述过编码规则,这里看一些实际的例子。
记住,下面这些字面量只是指定一个整数对象的值的一种替代方式。例如,在Python3.X和Python2.X中编写的如下字面量会产生具有上述3中机制底数的常规整数。在内存中,同一个整数的值是相同的,它与我们未来指定它而使用的底数无关。
这里,八进制的0o377,十六进制的0xFF和二进制的0b11111111,都表示十进制的255。例如,十六进制数值中的F数字表示十进制的15和二级的4位1111,且反映了以16为幂。因此,十六进制数值0xFF和其他数值能像下面这样转换为十进制数值
Python默认使用十进制显示整数数值,但它提供了内置函数,能帮我们把整数转换为其他进制的字符串。
oct函数会将会十进制转换为八进制数,hex函数会将十进制数转换为十六进制数,而bin函数会将十进制数转换为二进制。反过来,内置函数int会将一个数字的字符串转换为一个整数,并能够通过可选的第二位参数确定转换后数字的进制。这对于从文件中作为字符串读取的数字来说十分有用。
eval函数,将会把字符串作为Python代码来运行、因此,它也具有类似的效果,但往往运行的更慢。它实际上会把字符串作为程序的一个片段编译并运行,并且它假设当前运行的字符串术语一个可行的来源。耍小聪明的用户也许会提交一个删除你机器上文件的字符串,因此小心使用eval调用:
最后,你也可以使用字符串格式化方法调用和表达式(它只返回数字,而不是Python字面量字符串),将整数转换为指定底的字符串:
与之类似的有%,不过现在不推荐使用了
按位操作
除了一般的数学运算,Python也支持C语音中的大多数数学表达式。这些包括把那些把整数作为二级位串处理的运算,如果你的Python代码必须处理像网络数据包,串行端口或C程序产生的打包二级数控的这些内容,就会十分有用。
这里不再详细展开布尔数学的基本原理。那些必须使用它的人可能已经找到它的如何工作的,而其他人则通常会完全地后推对这一主题的学习,但是其基础知识是非常简单直接的。
可以看些我写的按位操作
第一个表达式中,二进制数1(以1为底数,0001)左移了两位成为二进制的4(0100)。上面的最后两个运算实现了一个二进制“或”来组合为(0001|0010 = 0011),以及一个“和”来选择共同的为(0001&0001 = 0001)。这样的位掩码运算,使我们可以对一个单独的整数编码和提取多个标志位和值。
在该领域中,从Python3.0和Python2.6开始引入的二进制和十六进制数变得特别有用,它们允许我们按照位字符串来编写和查看数字:
这也适用于通过十六进制字面量创建的数字,以及通过改变底数的数字
同样在这一部分中,Python3.1和2.7引入了一个新的整数方法bit_length,它允许我们查询二进制表示一个数字时所需的位数。通过利用第四章介绍的内置函数len,从bin字符串的长度减2,也可以得到同样的效果,(减2是减去字面量字符串开头的“0b”)但是这种方法系效率低。
这里不会涉及到更多关于位运算的细节。如果你需要,Python是支持的,但是按位运算在Python这样的高级语言中并不想在C这样的底层语言中那么重要。
其他内置数值工具
除了核心对象类型以外,Python提供了用于数值处理的内置函数和内置模块。例如,内置函数pow和abs用于计算幂和绝对值。下面是内置模块math中的一些例子。
我们之前介绍了截断和向下取整,而我们也可以用四舍五入,其目的既可以是求值,也可以是为了显示。
这里最后一个例子创建了通常用于打印的字符串,并且它支持多重格式化选项。
round会四舍五入并舍弃小数位数,但人会在内存中产生一个浮点数结果,然而字符串格式化将会产生一个字符串,而不是数字
Python中有3种方式可以计算平方根:使用一个模块函数,一个表达式或者一个内置函数。
要注意,内置模块在使用前需要先导入,但是想abs和round这样的内置函数则无须导入就可以直接使用。换句话说,模块是外部的组件,而内置函数则位于一个隐含的命名空间中,而Python会自动在这个命名空间中搜索程序中的名称。
标准库中的random模块在使用时也必须导入,该模块提供了一系列工具,可以完成诸如在0和1之间挑选一个随机浮点数,在两个数字之间挑选一个随机整数的任务。
random模块也能从一个序列中随机选取一项,并且随机地打乱列表中的元素。
尽管我们需要额外的代码使程序更加清晰明确,但random模块很实用,对于游戏中的洗牌,在演示GUI中随机挑选图片,进行统计模拟等都很有用处。
其他数值类型
目前,我们已经介绍了Python的核心数值类型:整数,浮点数以及复数。对于绝大多数程序员来说,这足以应对大部分的使用需求。然而,Python还自带了一些更少见的数值类型,值得我们在这里简要浏览一下,
小数类型
Python2.4中引入了一种新的核心数据类型:小数对象,其正式的名称是Decimal。从语法上来讲,需要通过调用已导入模块中的函数来创建小数,而不是通过运行字面量表达式来创建。从功能上讲,小数对象很像浮点数,但它们由固定的位数和小数点。因此,小数是精度固定的浮点数。
例如,使用小数对象,我们可以得到一个只保留两位小数精度的浮点数。此外,我们可以顶替如果省略和截断额外的小数数字。尽管这相对于一般的浮点数类型来说带来了性能上的损失,但小数类型对表达式固定精度的特性以及对实现更好的数值精度而言是一个理想的工具。
小数基础知识
下面是最后值得介绍的一点,正如我们在探索比较知识时简要预习过的,浮点数运算缺乏精确性,这是因为用来存储数值的空间有限,例如,下面的计算结果应该为零,但并非如此。其结果很接近0,但却没有足够的位数来实现这样的精度
在Python3.1和2.7之前的版本,打印结果会产生一个用户友好的显示格式,但并不能完全解决问题,因为与硬件相关的的浮点数运算在精确度方面有着内在的缺陷。
不过如果使用了小数对象,那么结果将根准确。
如你所见,我们可以通过调用decimal模块中的Decimal的构造函数来创建一个小数对象,并传入一个表示结果中显示小数位数的字符串。当你在表达式中混合不同精度的小数时,Python会自动转换为最高的小数位数。
你可以从一个浮点数对象创建小数对象,即通过decimal。Decimal.from_float(1.25)形式的调用来实现,而最新的Python版本允许直接使用浮点数来创建。这一转换是精确的,但是有时候会产生默认且庞大的小数位数。
在Python3.3以及之后的版本,小数模块的性能也得到了极大的提升,新版本宣称其速度提升了10到100倍,不过这取决于基于测试的程序种类
设置全局小数精度
decimal模块中的其他一些工具可以用来设置所有小数数值的精度,安排错误处理等。例如,该模块中的一个上下文对象可以指定精度(小数位数)和舍入模式(向下取整,向上取整等),该精度将会全局性的应用到调用线程中创建的所有小数。
对于处理货币这样的应用程序尤其重要,其中美分表示为两位小数位数。在这个上下文中,小数实际上是手动舍入和字符串格式化的一种替代方式
小数上下文管理器
你可以使用with上下文管理器唠临时重置小数精度。在with语句退出后,精度优惠重置为初始值。
由于小数类型在实际中仍然较少用到,因此请参考跑一趟获得标准库手册和交互式帮助来了解更多细节。
分数类型
Python2.6和Python3.0中首次引入了一种新的数值类型Fraction(分数),它实现了一个有理数对象。本质上,它显式地为此了一个分子和一个分母,从而避免了浮点数运算的某些不精确性和局限性。与小数一样,分数的实现并不像浮点数靠近计算机的底层硬件。这意味着它们的性能可能不会和浮点数一样优秀,但这是也也允许它们在必要时作为一种有用的标准工具。
分数基础知识
Fraction与上一小节介绍的Decimal固定精度类型十分相似,它们都可以用来处理浮点数类型的数值不准确性。分数的使用和小数很像。Fraction也位于模块中,你需要导入其构造函数,传入一个分子和一个分母,从而产生一个分数。
一旦创建了分数,它们就可以像平常一样用于实现表达式中:
分数也可以用过浮点数来创建
分数和小数中的数值精度
要注意,分数和小数中的数值精度与浮点数运算有所区别(浮点数运算受到浮点数硬件底层限制的约束)。出于比较目的。下面对浮点数对象进行相同的操作,注意到他们的不同精度
对于那些内存中给定的优先位数无法精确表示的值,浮点数的局限性尤为明显。Fraction和Decimal都提供了得到精确结果的方式,但这需要付出一些速度和代码冗余的代价。
此外,分数和小数都能够提供比浮点数更直观和准确的结果,它们以不同但是做到这一点,使用有理数表示以及通过限制精度
事实上,分数既保持了精确性,又自动简化了结果,
分数转换和混合类型
为了支持分数的转换,浮点数现在有一个方法能够产生它们的分子和分母比,分数有一个from_float方法,并且float函数可以接受一个Fraction对象作为参数。
最终,表达式中允许,有些类型的混个,尽管Fraction有时必须手动的传递以确保精确度,研究如下的交互示例来看看是如何做到的
警告,尽管你可以吧浮点数转换为分数,在某些情况下,这么做的时候会不可避免的精度损失。因为这个数字在其最初的浮点数形势下是不精确的。在不要时,我们可以通过限制分母的最大值来简化这样的结果
更多关于Fraction类型的细节查阅文档。
集合
除了小数以外,Python2.4还引入了一种新的类型----集合(set),这是一些唯一的,不可变的队形的一个无序集合体,这些对象支持与数学集合理论相对应的操作,按照定义,一个元素在集合中只能出现一次,不管它被添加了多少次,因此,集合有着官方的应用,尤其是在涉及数值和数据库的工作中。
集合是其他对象的集合体,它具有类表和字典这样对象的么些共同行为。如,集合是可迭代对象,可以按照需求增长或缩短,并且可以包含多种对象类型。集合的行为很像一个有键无值的字典,不过集合还支持更多的操作。
然而,集合是无序的,而且不会把键映射到值,因此它们既不是序列也不映射类型。它们是自成一体的类型。此外,集合本质上具有基本的数学特性。
集合特性,无序,唯一,确定
集合基础知识
要创建一个集合对象,你可以像内置的set函数传入一个序列或其他可迭代对象。
现在得到了集合对象,其中包含被传入的对象内的所有元素
集合是无序的,所以其中元素的顺序是任意的,版本,计算机等种种因素都可能使其发生变化。
集合通过表达式运算符支持一般的数学集合运算。要注意,我们不能对诸如字符串,列表和元组的一般序列使用下面的预算,我们必须将字符串,列表和元组传入set函数并创建了相应的集合后,才能使用这些工具。
该规则很明显的一个另外就是集合成员测试in.in表达式也定义为可以在全部其他集合体类型上工作,而其作用也就是成员测试。因此,我们不必将类似字符串和列表这样的数据类型转化为集合,就可以直接运行in测试
除了表达式,集合对象还提供了与这些操作相对应的方法,从而支持集合的修改,add方法插入一个项目,update在原位置求并集,remove根据值输出一个元素。
作为可迭代的容器,集合也可以用于len,for循环和列表推导这样的操作中,然而集合是无序的,所以不支持像索引和分片这样的操作
尽管书上介绍这对于任何迭代类型都是有效的,但是我想时代是真的变了
关于创建集合,现在下面的两种方法是相同的。
集合基本上就像是没有值的字典。字典的键值表在Python3.X中是视图对象,因而它能支持像交集和并集这样类似集合的行为。
不管一个集合是如何被创建的,Python3.X都能够只用新的字面量来显示它、在所有的python版本中,如果要创建空的集合,还是需要通过内置函数set函数。如果你想用{}表示空集合,那么空字典该怎么办
不可变性与冻结集合
集合是强大而灵活的对象,但是在Python中有一个限制需要铭记,集合只能包含不可变的对象类型,因此,类表和字典不能嵌入到集合中,但是如果你需要存储复合对象的话,元组是可以嵌入集合的。元组在集合操作中会比较其完整的值:
集合中的元组可以用来表示日期,记录,IP地址等。集合也可以包含没亏啊,类型对象等。集合本身也是可变的,因此,不能后直接就嵌入到其他集合中,如果需要在另一个集合中存储一个集合,可以像调用set一样调用函数frozenset,但frozenset会创建一个不可变的集合,该集合不可修改,并且可以嵌入到其他集合中。
不是说集合不可改变,而是集合中的内容是不可变的,具有确定性,如元组,字符串,数字。
集合推导
集合推导表达式类似于第四章介绍的列表推导的形式,但它编写在花括号中而不是方括号中,并且会创建一个集合而不是列表。集合推导会运行一个巡航并在每次迭代时手机一个表达式的结果,通过一个循环变量来访问当前的迭代值以用于集合表达式中、其结果就是通过运行代码创建一个新的集合,它具有所有一般的集合行为。
在该表达式中,循环部分编写在右侧,而集合体表达式编写在左侧。对于列表中的每一个X,给出包含X平方的一个新集合。对到也刻意迭代其它类型的对象,例如字符串
为什么使用集合
集合操作有各种各样的常见用途,其中一些比其数学意义更加使用,例如,去除重复项,利用无序进行重新排序。你只需要啊集合体转换为一个集合然后再转换回来即可。
集合也可以用于用于提取列表,字符串以及其他可迭代对象中的差异,当然,集合的无序性本质意味着结果可能和原理不匹配。
也可以通过转换成集合,借助集合进行顺序无关的等价性测试,这是因为顺序在集合中不重要,更正式的说,两个集合相当仅当两个集合中的每一个元素都被另一个集合所包含。也就是说,撇开顺序,每一个集合都是另一个集合的子集,
例如,你可能会比较程序的输出,程序应当同样的工作但是也许会产生不同顺序的结果。在测试进行前进行排序可以获得等价性测试的效果,但是几个不需要开销高昂的排序,而排序允许其结果支持集合所不支持的相对大小比较。
当你遍历一个图或者其他有环结构时,集合可以用于记录已访问的位置。最后,当你在处理较大的数据时,两个集合的交集包含了两个种类中共有的对象,并集包含了两个集合中的所有元素。
你可以在Python库手册以及关系数据库理论的一些相关资料中,找到关于集合操作的更多细节。
布尔型
有些人可能认为Python的布尔类型本质上是数值,因为它包含两个值True和False,而且就算整数1和0的定制版,只不过打印时有所不同。尽管这是大多数程序员所需了解的全部,我们还是要稍稍深入的探索布尔类型
更正式的说,今天的Python有一个名为boll的显示布尔数据类型,带有True和False作为可用且预赋值的内置名称。在内部,名称True和False是bool的示例,而bool实际上只是内置整数类型int的之类(从面向对象的角度来看)。True和False的行为与在说话1和0是一样的,只不过他们有独特的显示逻辑:他们是作为关键字True和False显示的,而不是整数1和0.bool为这两个对象重新定义了str和repr的字符串格式
由于该定制,布尔类型表达式在交互式命令行模式的输出就成为关键字True和False来显示,而不是曾经的1和0。此外,布尔类型让真值在你的代码中更加明显。例如,一个无限循环现在可以写为while True:而不是更不直观的while 1:。类似的,通过使用flag = False可以更清楚的设置标志位。
同样,对于大多数实际场景,你可以把True和False看做预定义的设置为整数1和0的名称。不过很多程序员都曾把True和False预先赋值为1和0,所以新的bool类型直接让其成为一种标准。但它的实现会导致奇怪的结果。因为True仅仅是定制了显示格式的整数1、所以在Python中Trued++4得到了整数5
因为你可能不会在真正的Python代码中遇到像上面例子中最后一个那样的表达式,所以你完全可以忽略其任何更深奥的形而上的含义。
数值扩展
尽管跑一趟后的核心数值类型提供的功能对于大多数应用程序而言已经足够用了,但还有大量的第三方开源扩展可以用来解决更加专门的需求。
例如,如果你需要做一些正式的数字计算,一个叫做NumPy的可选Python扩展提供了高级的数值编程工具,例如矩阵数据类型,向量处理和精密的计算库。
本章小结
本章介绍了Python数值对象及其运算。在这个过程中,我们学习了表核准的整数和浮点数类型,以及一些更加奇特和少见的类型,例如复数,小数,分数和集合。我们也学习了Python的表达式语法、类型转换、按位运算以及各种在程序中编写数字的字面量形式。
接下来在本书的这一部分中,我们将继续更深入的类型流程,并继续学习下一个对象类型----字符串的一些细节,然而在下一章中,我们将花一些时间来探索这里用到的变量赋值机制以及更多细节。这也行是Python中最基本的概念,所以在继续学习之前,你需要好好阅读下一章,不过首先,让我们照例做一下章节测试。
回顾一下本章内容,首先是数值类型种类,它们的字面量,然后是表达式运算符,运算的优先级。了解一下变量,数值的显示格式,str和repr,链式比较。关于Python3.X的除法,/是真除法,//是向下取整除法。复数简单了解一下格式。进制转换,本质,一些按位操作。
小数与分数,其意义以及表示方法。集合相关知识。3种特性,无序,唯一,确定,及其应用。布尔类型。知道一个NB的库NumPy。
下面背诵并默写本章习题
本章习题
1.Python中的表达式2 * (3 + 4)的值是多少?
结果是14,即2*7的结果,因为括号强制让加法在乘法之前运算。
2.Python中表达式2 * 3 + 4 的值是多少?
结果是10,即6+4的结果。Python的运算符优先级法则适用于没有括号存在的场合,按照表5-1,除法的优先级要比加法的优先级高(先进行乘法运算)。
3.Python中表达式2 + 3* 4 的值是多少?
结果是14,即2+12的结果,与上一题一样是优先级的原因。
4。你可以使用寿命工具来计算一个数字的平方根以及它的平方?
当你导入math模块后,即可求平方根、pi以及正切等函数。为了获得一个数字的平方根,import math后调用math。sqrt(N)。为了得到一个数字的平方,使用指数表达式X ** 2,或者内置函数pow(X,2).上述两种方式都可以用来计算一个数的0.5次方(例如X ** .5)
5.表达式1 + 2. 0 +3 的结果是什么类型?
结果是一个浮点数,整型将转换升级成浮点数,也就是该表达式中最复杂的类型,然后采用浮点数的运算法则进行计算。
6.如何截断或舍去浮点数的小数部分?
int(N)函数和math。trunc(N)函数可以省略小数部分,而round(N,digit)函数会四舍五入。我们可以使用math.floor(N)来计算向下取整,并且使用字符串格式化操作来舍入以便于显示。
7.如果讲一个整数转换为浮点数?
float(N)将整数转换为浮点数,在表达式中混合整数和浮点数也会实现转换。在某种意义上,Python3.X的 / 除法也会转换,它总是返回一个包含小数部分的浮点数结果,即便两个操作数都是整数。
8.任何将一个整数显示成八进制、十六进制或二进制的形式?
内置函数oct(I)和hex(I)会将整数以八进制和十六进制数字符串的形式返回。在Python2.6.3.0以及之后的版本中,bin(I)也会返回一个数字的二进制数字字符串,%字符串格式化表达式和字符串format方法也可以进行这样的转换。
9.任何将一个八进制、十六进制或二进制的字符串转换成一般的整数?
int(S,base)函数可以用来将一个八进制和十六进制的字符串转换为支持的整数(传入8、16或2作为base参数)。eval(S)函数也能够用作这个目的,但是运行起来开销更大且可能导致安全问题。注意整数总是在计算机内存中以二进制形式存储;这些只不过是显示字符串格式的转换而已。