Python学习手册 ——— 数值类型

  • 基础知识
  • 数值字面量
  • 整数和浮点数对象
  • 进制
  • 复数对象
  • 小数类型
  • 分数类型
  • 集合
  • 布尔型
  • 内置数值工具
  • 表达式运算符
  • 混合运算遵循运算符优先级
  • 括号分组子表达式
  • 混合类型向上转换
  • 数字的实际应用
  • 变量与基础表达式
  • 普通比较与链式比较
  • 除法:真除法和向下取整除法
  • 按位操作
  • 其他内置数值工具
  • 数值拓展


基础知识

在Python中,数字并不只是一种对象类型,而是一组相似类型的分类。

数值字面量

字面量

解释

1234, -24, 0

整数

1.23, 3,14e-10, 4E210

浮点数

0o177, 0x9ff, 0b1010

八进制、十六进制、二进制

3+4j, 3J

复数

set(‘Python’), {1, 2, 3, 4}

集合

Decimal(‘1.0’), Fraction(1, 3)

小数和分数扩展

bool(X), True, False

布尔类型

整数和浮点数对象

整数写成十进制数字的串,一般整数和长整数类型已经合二为一。整数在程序中不再有末尾的lL表示,因为当整数的值超过为其分配的位数的时候会自动转换为长整数,当需要额外的精度时,Python会自动转换为长整数。

浮点数带一个小数点,可以加上一个科学计数标志e或者E。在编写带有小数点或幂的数字时,Python会自动创建为浮点数对象,并在该对象所在的表达式中启用浮点数的运算法则。

进制

整数可以编写为十进制、十六进制、八进制和二进制形式。

十六进制以0x0X开头,后面接十六进制的数字0-9A-F。十六进制的数字编写成大写或小写都可以。hex()函数会将十进制数转换为十六进制。

八进制以数字0o0O开头(数字0后加小写或答谢字母o)。后面接着数字0-7构成的字符串。oct()函数会将十进制数转换为八进制。

二进制以0b0B开头,后面跟着二进制数字01bin()函数会将十进制数转换为二进制。

内置函数int()函数会将一个数字的字符串转换为一个整数,并能通过可选的第二参数来确定转换后数字的进制。

复数对象

Python的复数写成实部+虚部的形式,虚部以jJ结尾,支持所有一般的数学表达式实部从技术上讲可以省略,虚部可以独立于实部单独存在。从内部看来,复数是通过一对浮点数来实现的,对复数的所有数字运算都会按照复数的运算法则进行。复数可以通过内置函数complex(real, imag)来创建。

小数类型

小数对象,其正式名称是Decimal。从语法上讲,需要通过调用已导入模块中的函数来创建小数,而不是通过运行字面量表达式来创建。从功能上讲,小数对象很像浮点数,但它们有固定的位数和小数点。因此,小数是精度固定的浮点数。

例如, 使用小数对象,可以得到一个只保留两位小数位精度的浮点数。此外,还可以定义如何省略和截断额外的小数数字。尽管这相对于一般的浮点数类型来说带来了性能上的损耗,但小数类型对表达固定精度的特定(例如货币的累加)以及对实现更好的数值精度而言,是一个理想的工具。

小数基础知识
浮点数运算缺乏精确性,这是因为用来存储数值的空间有限。例如,下面的结算结果应该为0,但并非如此,结果很接近0,但却没有足够的位数实现这样的长度:

>>> 0.1 + 0.1 + 0.1 - 0.3
5.551115123125783e-17

因为与硬件相关的浮点数运算在准确度方面有着内在缺陷,若使用小数对象,那么结果将更准确:

>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')
Decimal('0.0')

通过调用decimal模块中的Decimal的构造来创建一个小数对象,并传入一个表示结果中显示小数位数的字符串。当在表达式中混合使用不同精度的小数时,Python会自动转换为最高的小数位数

设置全局小数精度
decimal模块中的其他一些工具可以用来设置所有小数数值精度,安排错误处理等。

>>> import decimal
>>> decimal.getcontext().prec = 4
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal('0.1429')

小数实际上是手动舍入和字符串格式化的一种代替方式。

小数上下文管理器
可以使用with上下文管理器语句来临时重置小数精度。在with语句退出后,精度又会重置为初始值:

>>> import decimal
>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.3333333333333333333333333333')

>>> with decimal.localcontext() as ctx:
...     ctx.prec = 2
...     decimal.Decimal('1.00') / decimal.Decimal('3.00')
... 
Decimal('0.33')

>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.3333333333333333333333333333')

分数类型

Python中引入了**Fraction(分数)**的数值类型,实现了一个有理数对象。本质上,它显式地保持了一个分子和一个分母,从而避免了浮点数运算的某些不准确性和局限性。与小数一样,分数的实现并不像浮点数靠近计算机的底层硬件。

分数基础知识
FractionDecimal固定精度类型十分相似,都可以用来处理浮点类型的数值不精确性。Fraction需要导入其构造函数并传入一个分子和一个分母,从而产生一个分数:

>>> from fractions import Fraction
>>> Fraction(1, 3)
Fraction(1, 3)

>>> print(Fraction(1, 3))
1/3

一旦创建了分数,它们就可以像平常一样用于数学表达式中,同时也可以通过浮点数字符串来创建,这和小数很相似:

>>> Fraction('1.25')
Fraction(5, 4)

分数和小数中的数值精确度
分数和小数的数值精度与浮点数运算有所区别(浮点数运算收到浮点数硬件底层限制约束)。最新的Python版本比之前的版本可能会显示更少的位数,但在内存中它们仍然是不准确的。
对于用内存中给定的有限位数无法精确表示的值,浮点数局限性尤为明显。FractionDecimal都提供了得到精确结果的方式,但这需要付出一些速度和代码冗余性的代价。

>>> 0.1 + 0.1 + 0.1 - 0.3
5.551115123125783e-17

>>> from fractions import Fraction
>>> Fraction(1, 10) + Fraction(1, 10) + Fraction(1, 10) - Fraction(3, 10)
Fraction(0, 1)

>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')
Decimal('0.0')

可以看出,浮点数并不能准确地给出期望的答案0,但小数和分数都做到了。实际上分数既保持了准确性,又自动简化了结果:

>>> from fractions import Fraction
>>> Fraction(100, 1000)
Fraction(1, 10)

分数转换和混用类型
为了支持分数的转换,浮点数对象现在有一个方法,能够产生它们的分子和分母比,分数有一个from_float()方法,并且float()函数可以接受一个Fraction对象作为参数:

>>> (2.5).as_integer_ratio()
(5, 2)

>>> from fractions import Fraction
>>> Fraction.from_float(1.75)
Fraction(7, 4)

>>> float(Fraction(7, 4))
1.75

注意:尽管可以把浮点数转换为分数,但这不可避免会带来精度损失,因为这个数字在最初的浮点数形式下是不精确的。在必要时会限制分母的最大值来简化结果。

>>> (Fraction(234234, 3425234)).limit_denominator(10)
Fraction(1, 10)

集合

集合(set),是一些唯一、不可变的对象的一个无序集合体,这些对象支持与数学集合理论相对应的操作。按照定义,一个元素在集合中只能出现一次,因此,集合在涉及数值和数据库工作中有广泛应用。

因为集合是其他对象的集合体,因此它具有列表和字典的某些共同行为。例如,集合是可迭代对象,可以按需增长或缩短,并且可以包含多种对象类型。集合的行为很像一个有键无值的字典,不过集合支持更多操作。

由于集合是无序的,而且不会把键映射到值,因此它们既不是序列也不是映射类型,集合本质上具有基本的数学特性。

集合基础知识
要创建一个集合对象,可以向内置的set()函数传入一个序列或其他可迭代对象:

>>> set('abcd')
{'a', 'd', 'c', 'b'}

因为集合基本上就像是无值的字典,因此集合中元素的行为与字典的键很像,也可以使用集合字面量形式创建:

{'a', 'd', 'c', 'b'}

集合中元素顺序是任意的。要注意,我们不能对诸如字符串、列表和元组的一般序列使用下面的运算,我们必须将字符串、列表和元组传入set()函数,并创建了相应的集合后,才能使用这些工具:

>>> x = set('abcde')
>>> y = set('bdxyz')

>>> x - y
{'e', 'c', 'a'}

>>> x | y
{'x', 'a', 'y', 'z', 'c', 'e', 'd', 'b'}

>>> x & y
{'d', 'b'}

>>> x ^ y
{'y', 'z', 'e', 'c', 'a', 'x'}

>>> x > y
>False

in表达式也定义为可以在全部其他集合体类型上工作,而其作用也是进行成员测试,因此不必将类似字符串和列表这样的数据类型转换为集合,就可以直接运行in测试:

>>> 'e' in x
True

除了表达式,集合对象还提供了与这些操作相对应的方法,从而支持集合的修改:

  • 集合的add()方法插入一个项目;
  • uodate在原位置求并集
  • remove根据值删除一个元素
    任何集合实例或set类型名上运行dir(),都可以查看所有可用的方法。

作为可迭代的容器,集合也可以用于len()for循环和列表推导操作中。但由于集合是无序的,所以不支持像索引分片这样的操作

布尔型

Python中有一个名为bool的显式布尔数据类型,带有TrueFalse作为可用且预赋值的内置名称。在内部,名称TrueFalsebool的实例,而bool实际上只是内置整数类型int的子类(从面向对象的角度来看)。bool重新定义了strrepr的字符串格式。

由于大多数实际场景,可以把TrueFalse看作预定义的设置为整数10的名称。

内置数值工具

Python提供了一系列处理数字对象的工具:

表达式运算符

表达式是处理数字最基本的工具,表达式的定义是:数字(或其他对象)与运算符相组合,并被Python在执行时计算为一个值。可以使用一般的数学记号与运算符号编写表达式。

运算符

描述

yield x

生成器函数send协议

lambda args: expression

创建匿名函数

x if y else z

三元选择表达式(仅当y为真时, x才被计算)

x or y

逻辑或(仅当x为假时,y才会被计算)

x and y

逻辑与(仅当x为真时,y才会被计算)

not x

逻辑非

x in y, x not in y

成员关系(可迭代对象、集合)

x is y, x not is 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 - y

减法、集合差集

x * y

乘法、重复

x % y

求余数、格式化

x / y, x // y

真除法、向下取整除法

-x, +x

取负、取正

~x

按位非(取反码)

x ** y

幂运算(指数)

x[i]

索引(序列、映射等)

x[i : j : k]

分片

x(…)

调用(函数、方法、类,其他可调用对象)

x.attr

属性引用

(…)

元组、表达式、生成器表达式

[…]

列表、列表推导

{…}

字典、集合、集合与字典推导

混合运算遵循运算符优先级

当编写含有一个运算符以上的表达式时,Python讲按照所谓的优先级法则对表达式进行分组,这个分组决定了表达式中各部分的计算顺序。

  • 在刚刚给出的运算符表中,从上到下优先级逐渐增高;
  • 位于同一行的表达式在分组的时候通常按照从左到右组合(除了幂运算从右向左组合,还有比较运算,是从左到右链接)

括号分组子表达式

用圆括号()将表达式各部分进行分组,将会超越Python的优先级规则,会优先计算圆括号中的表达式。一般来说,在一个大型表达式中添加括号是个很好的做法,不仅可以强制按照想要的顺序计算,也增加了程序的可读性。

混合类型向上转换

除了在表达式中混合运算以外,也可以混合数值类型,例如把整数与浮点数相加等。
在混合类型的表达式中,Python首先将被操作的对象转化成其中最复杂的操作数类型,然后在对相同类型的操作数进行数学运算。

Python是这样划分数值类型复杂度的:

  • 整数比浮点数简单;
  • 浮点数比复数简单;

任何混合类型的表达式,其中一个操作数是更复杂的数字,就会导致其他的操作数升级为一个复杂的数字,使表达式获得一个复杂的结果。

同时也可以手动调用内置函数来强制转换类型:

>>> int(3.14)
3

>>> float(3)
3.0

所有的混合类型转换仅适用于数值类型(例如一个整数和浮点数)的混合,这包括那些使用数字和比较运算符的表达式。

数字的实际应用

变量与基础表达式

在Python中:

  • 变量在第一次赋值时被创建;
  • 变量在表达式中使用时,会被替换成它们的值
  • 变量在表达式中使用之前,必须已被赋值;
  • 变量引用对象,而且从不需要事先声明。

在Python 2.6、 Python 3.0或更早的版本中:

>>> 4 /(2.0 + 3)
0.80000000000000004

>>> print(4 / (2.0 + 3))
0.8

这个奇怪的结果是因为浮点数的硬件限制,以及浮点数无法精确地表示一些值。实际上,这个是一个显示问题,在交互式命令行下的自动结果会比这里的print语句显示更多的数字位数。它们在内存中对应的是相同的数字,Python的浮点显示逻辑尝试变的更加智能,经常能显示较少的位数,但偶尔会更多。

普通比较与链式比较

一般的比较能像我们所期待的那样作用于数字,会比较操作数的相对大小,并且返回一个布尔类型的结果。

>>> 1 < 2
True

Python允许把多个比较链接起来执行范围测试。链式比较是更大的布尔表达式的简写。

>>> 2 < 4 < 6
True

>>> 2 < 4 and 4 < 6
True

两种表达式写法等同,但第一个表达式更简单且便于录入,同时Python只需计算一次,在运行上更快一些。
Python允许任意的链式长度,但最终的表达式可能会变得很晦涩。

>>> 1 == 2 < 3
False

因为1不等于2:
False < 3 等价于 0 < 3

数值比较是基于相对大小的,但浮点数需要借助转换或其他处理才能进行有意义的比较:

>>> 1.1 + 2.2 == 3.3
False

>>> 1.1 + 2.2
3.3000000000000003

由于浮点数因为有限的比特位数,不能精确地表示某些值的事实,这是数值编程的一个基本问题,而不只是在Python中出现。

除法:真除法和向下取整除法

X / Y
真除法,即无论任何类型,最终的浮点数结果都会保留小数部分。

X // Y
向下取整除法,不考虑操作对象类型,总是会省略结果的小数部分,剩下最小的能整除的整数部分。结果取决于操作数的类型,如果任何一个操作数是浮点数类型,则返回一个浮点数。

>>> 10 / 4
2.5

>>> 10 // 4
2

向下取整除法截断除法
//运算符有一个非正式的别名,叫做截断除法,把结果向下截断到它的下层,即真正结果之下的最近的整数。其直接效果是向下舍入,并不是严格地截断,并且这对负数也有效。

>>> -10 // 4
-3

若想获得结果不管正负都趋于0的截断,可以使用math.trunc函数:

>>> import math
>>> math.trunc(5 / 2)
-2

Python除了截断和向下取整外,还支持四舍五入:

>>> round(2.567), round(2.467)
(3, 2)

>>> '%.1f' % 2.567, '{0:.2f}'.format(2.567)
('2.6', '2.57')

函数会四舍五入并舍弃小数位数,但仍会在内存中产生一个浮点数结果,然而字符串格式化将产生一个字符串,而不是数字。

按位操作

将整数作为二进制位串,在处理网络数据包、串行端口或C程序产生的打包二进制数据这些内容时,十分有用。
但Python的列表、字典以及其他数据结构对于反转位提供了比数字串更为丰富且实用的编码信息的方式,且可读性更强。

其他内置数值工具

Python提供了用于数值处理的内置函数和内置模块。例如,内置函数pow()abs()用于分别计算绝对值

>>> import math
>>> math.pi
3.141592653589793

>>> math.sin(2 * math.pi /180)
0.03489949670250097

>>> sum((1, 2, 3, 4))
10

sum()函数作用于一个数字的序列,还有min()max()函数接受一个参数序列或者多个单独参数,取其最小值或最大值。

Python中有3种方式可以计算平方根:实用一个模块函数、一个表达式或者一个内置函数。

>>> import math
>>> math.sqrt(144)
12.0

>>> 144 ** .5
12.0

>>> pow(144, .5)
12.0

内置模块math在使用前必须先导入,但像abs()sum()这样的内置函数则无需导入就可以直接使用。模块是外部组件,而内置函数则位于一个隐含的命名空间内,而Python会自动在这个命名空间中搜索程序中的名称。这个命名空间直接对应于Python3中名为builtins的标准模块。

标准库中的random模块在使用时也必须导入,该模块能够随机挑选一个浮点数,从序列中随机选取一项等:

>>> import random
>>> random.random()		# 随机0-1浮点数
0.5566014960423105

>>> random.randint(1, 10)	# 1-10中随机整数
5

>>> random.choice(['Hello', 'World', 'Python'])	  # 列表中随机一项
'Python'

>>> random.shuffle(['Hello', 'World', 'Python'])  # 列表随机排序
['World', 'Hello', 'Python']

数值拓展

由于数值编是Python一个很热门的领域,因此有大量第三方开源拓展可以用来解决专门的需求。
例如需要做一些正式的数字计算,NumPy的可选拓展提供了高级的数值编程工具,例如矩阵数据类型,向量处理和精密的计算库。