【Python学习笔记】 第5章 数值类型
数值类型基础知识
在Python中,数值实际上是一种类型的分类,包括:
-
整数、浮点数
-
复数
-
小数:固定精度对象
-
分数:有理数对象
-
集合:带有数值运算的集合体
-
布尔值:
True
,False
-
内置函数与块:
round
,math
,random
等 -
表达式、无限制精度整数(很大的整数)、位运算、八进制、十二进制、二进制
-
第三方扩展:向量、库、可视化、作图等
其中整数、浮点数最为常见,因此先从它们讲起。
数值字面量
字面量 | 解释 |
---|---|
1234, -24, 0, 99999999999999 |
整数(无大小限制) |
1.23, 1., 3.14e-10, 4E210, 4.0e+210 |
浮点数 |
0o177, 0x9ff, 0b101010 |
Python 3.X中八进制、十六进制、二进制字面量 |
0177, 0o177, 0x9ff, 0b101010 |
Python 2.X中两种八进制、十六进制、二进制字面量 |
3+4j, 3.0+4.0j, 3J |
复数字面量 |
set('spam'), {1, 2, 3, 4} |
集合 |
Decimal('1.0'), Fraction(1, 3) |
小数、分数扩展 |
bool(X), True, False |
布尔类型、字面量 |
注意:
-
浮点数带小数点,或加上科学技术标志
e/E
。如果字面量带有上述描述,那么将其识别为浮点数。 -
整数:在Python2中有32位整数和长整数(无限制长),Python3中只有整数,即无限长的整数。
-
十六进制、八进制和二进制字面量:八进制以
0o
或0O
开头,十六进制以0x
开头,二进制以0b
或0B
开头。 -
复数:字面量位实部+虚部,虚部以
j
或J
结尾。
内置数值工具
处理数字的工具包括:
-
表达式运算符
-
内置数学函数
-
工具模块
它们也有一些专用于特定类型的方法。
Python表达式运算符
表达式的定义:数字/变量与运算符相结合,在执行时计算为一个值。
Python所有的运算符表达式如下:
运算符 | 描述 |
---|---|
yield x |
生成器函数send 协议 |
lambda args: expression |
创造匿名函数(lambda表达式) |
x if y else z |
三元选择表达式,如果y 为真则取x ,否则取z |
x or y |
逻辑或 |
x and y |
逻辑与 |
not x |
逻辑非 |
x in y , x not in y |
x 是/不是y 的成员 |
x is y , x is not y |
`对象是否同一(不止是值相等) |
x < y , x > y , x <= y , x >= y |
x 是否小于/大于/不大于/不小于y |
x == y , x != y |
x 等于/不等于y (和上面的is 不一样) |
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 的y 次幂 |
x[i] |
索引 |
x[i:j:k] |
分片 |
x(...) |
调用函数/方法/类等等 |
x.attr |
属性引用 |
(...) |
元组、表达式、生成器表达式 |
[...] |
列表、列表推导 |
{...} |
字典、集合、字典与集合推导 |
混合运算遵循运算符优先级
-
以上表为标准,运算符从上到下优先级逐渐升高;
-
如果优先级相同,则从左到右运算。
括号分组子表达式
如果我们使用了括号,就不用考虑优先级,因为Python优先计算括号内的子表达式,再计算整个表达式。
混合类型向上转换
如果表达式中有复数,那么结果就为复数;如果表达式中只有整数和浮点数,则结果为浮点数;如果表达式中只有整数,则结果为整数(除非有除法)。
我们也可以通过强制转换的方法选择想要的类型:
>>> int(3.1415)
3
>>> float(3)
3.0
预习:运算符重载和多态
我们在之前也看见,+
也适用于字符串拼接。这是因为,字符串类型对+
进行了重载。用户定义的新类型也可以重载+
。
Python的这个特性叫多态。
数字的实际应用
变量与基础表达式
变量是名称,用于记录程序中的信息。在Python中:
-
变量在第一次赋值时被创建
-
变量在表达式中被使用时,会被替换为它们的值
-
变量在表达式中使用之前,必须已经赋值
-
变量引用对象,不需要事先声明
例:下面的例子中,自动创建变量a
和b
:
>>> a = 3
>>> b = 4
在表达式中使用刚刚创建的变量,此时变量替换为它们的值:
>>> a + 1, a - 1
(4, 2)
>>> b * 3, b / 2
(12, 2.0)
>>> a % 2, b ** 2
(1, 16)
>>> 2 + 4.0, 2.0 ** b
(6.0, 16.0)
注意到,我们输入了多个表达式并用逗号分隔它们,这样Python对每个表达式分别求值,并将它们用元组的方式组合起来。
如果使用还没创建的变量,则Python会报错:
>>> c * 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'c' is not defined
Python解释数值表达式的时候,计算的先后顺序与数学上的优先顺序一致(如先乘除后加减,如果有括号先算括号)
数值的显示格式
在早一些版本,会出现:
>>> b / (2.0 + a)
0.80000000000000004
>>> print(b / (2.0 + a))
0.8
后面的版本已经解决了这个问题。我们也可以用其他方法显示计算机中数字的位数(比如直接在交互界面输入变量名,或者使用字符串格式化):
>>> num = 1 / 3.0
>>> '%e' % num
'3.333333e-01'
>>> '%4.2f' % num
'0.33'
>>> '{0:4.2f}'.format(num)
'0.33'
普通比较与链式比较
普通数值可以进行比较,并返回一个布尔值。
>>> 2.0 >= 1
True
>>> 2.0 != 2.0
False
但是,一次比较只能比较两个数,因此Python运行我们把多个比较连接起来:
>>> X = 2
>>> Y = 4
>>> Z = 6
>>> X < Y < Z
True
>>> X < Y and Y < Z
True
实际上,前者更有效率,因为Python只计算一次Y。
比较链可以是任意形式,也可以是任意长度。
>>> X < Y > Z
False
>>> 1 < 2 < 3.0 < 4
True
如果需要判断表达式是否相等,且用到浮点数,那么对其额外处理(如判断两者的差的绝对值是否小于一个很小的数):
>>> 0.1 + 0.2 == 0.3
False
>>> 0.1 + 0.2
0.30000000000000004
除法、经典除法、向下取整除法和真除法
-
x / y
:在Python2.X中,整数取省去小数部分,浮点数保留余项;在Python3.X中,无论用何种类型,都会变成真除法(即保留小数部分)。 -
x // y
:向下取整除法。
>>> 10 / 4
2.5
>>> 10 / 4.0
2.5
>>> 10 // 4
2
>>> 10 // 4.0
2.0
可见,//
依赖于操作数的类型。
支持两个Python脚本
在Python 2.X中,要实现像Python 3.X的除法效果,需要从模块__future__
导入division
。
向下取整除法 vs 截断除法
除法//
是向下截断除法。对浮点数采用向下截断的函数是math.floor
,向零截断的函数是math.trunc
,效果如下:
>>> import math
>>> math.floor(2.5)
2
>>> math.floor(-2.5)
-3
>>> math.trunc(2.5)
2
>>> math.trunc(-2.5)
-2
在Python中,截断除法//
总是向下取整。
>>> 5 / 2, 5 / -2
(2.5, -2.5)
>>> 5 // 2, 5 // -2
(2, -3)
>>> 5 / 2.0, 5 / -2.0
(2.5, -2.5)
>>> 5 // 2.0, 5 // -2.0
(2.0, -3.0)
为什么截断很重要
Python 3.X中的非截断行为会严重影响大量的Python 2.X程序。
整数精度
Python 3.X支持无限制的整数大小,但Python 2.X识别长整数时在末尾加一个L。这方便了高精度运算(在C/C++需要额外实现)。
复数
复数的后缀为j
或J
,我们可以把实部非0的复数写成实部与虚部相加的形式。比如,实部为2,虚部为-3的复数可以写为2 + -3j
或2 - 3j
。
复数运算的例子:
>>> 1j * 1J
(-1+0j)
>>> 2 + 1j * 3
(2+3j)
>>> (2 + 1j) * 3
(6+3j)
复数允许访问实部和虚部,并支持所有数学表达式和cmath
模块中的数学函数。
十六进制、八进制和二进制:字面量与转换
在Python中,编写以0o
或0O
为开头的字面量,则将其识别为八进制;以0x
或0X
为开头的字面量,则将其识别为十六进制;以0b
或0B
为开头的字面量,则将其识别为二进制。
>>> 0o1, 0o20, 0o377
(1, 16, 255)
>>> 0x01, 0X10, 0xFF
(1, 16, 255)
>>> 0b1, 0B10000, 0b11111111
(1, 16, 255)
Python默认用十进制显示数值。如果要将一个数转换为八进制、十六进制和二进制,可以分别用oct
、hex
、bin
。
>>> oct(64), hex(64), bin(64)
('0o100', '0x40', '0b1000000')
内置函数int
将一个(十进制、八进制、十六进制、二进制)数字字符串转换为一个整数,第一个参数是要转换的字符,第二个参数是要把这个数字字符串看作是几进制,不填则默认为十进制。
>>> int('64'), int('100', 8), int('40', 16), int('1000000', 2)
(64, 64, 64, 64)
>>> int('0x40', 16), int('0b1000000', 2)
(64, 64)
eval
函数把字符串作为Python代码运行。
字符串格式化方法和表达式把整数转换为指定的字符串:
>>> '{0:o}, {1:x}, {2:b}'.format(64, 64, 64)
'100, 40, 1000000'
>>> '%o, %x, %x, %X' % (64, 64, 255, 255)
'100, 40, ff, FF'
注意,在Python3.X中用0开头的数字表示八进制会引发错误。其次,八进制、十六进制和二进制都可以是任意长度的整数。
>>> X = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
>>> X
25711008708143844408671393477458601640355247900524685364822015
>>> oct(X)
'0o77777777777777777777777777777777777777777777777777777777777777777777'
>>> bin(X)
'0b111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
按位操作
Python支持C语言中的大多数数学表达式,比如把整数作为二进制串处理的运算(位运算)。如:左移、右移、逻辑与/非/或/异或。
>>> x = 1 # 0001
>>> x << 2 # 0100
4
>>> x | 2 # 0001 | 0010 = 0011
3
>>> x & 1 # 0001 & 0001 = 0001
1
在位运算下,之前介绍的二进制、十六进制就十分有用(因为十六进制的每一位对应二进制的四位)。
Python引入了新的整数方法bit_length
,查询二进制表示一个数字的值时至少需要的位数。
>>> X = 99
>>> bin(X), X.bit_length(), len(bin(X)) - 2
('0b1100011', 7, 7)
事实上,位运算在Python中不太重要。
其他内置数学工具
内置模块math
:
>>> import math
>>> math.pi, math.e # 数学常数
(3.141592653589793, 2.718281828459045)
>>> math.sin(2 * math.pi / 180) # 三角函数
0.03489949670250097
>>> math.sqrt(144), math.sqrt(2) # 算术平方根
(12.0, 1.4142135623730951)
>>> pow(2, 4), 2 ** 4, 2.0 ** 4.0 # 幂运算
(16, 16, 16.0)
>>> abs(-42.0), sum((1, 2, 3, 4)) # 绝对值、和
(42.0, 10)
>>> min(3, 1, 2, 4), max(3, 1, 2, 4) # 序列的最小值、最大值
(1, 4)
模块math
包含多种取整方式:
>>> math.floor(2.567), math.floor(-2.567) # 向下取整
(2, -3)
>>> math.trunc(2.567), math.trunc(-2.567) # 向零取整
(2, -2)
>>> int(2.567), int(-2.567)
(2, -2)
>>> round(2.567), round(2.467), round(2.567, 2) # 四舍五入
(3, 2, 2.57)
注意到,优先函数如sin
需要导入math
,有些则不用。因为这些函数是内置函数,位于隐秘的命名空间内,对应于Python的builtins
模块。
随机数random
也需要导入。它可以选择在区间\([0, 1]\)的任意实数:
>>> import random
>>> random.random()
0.83057242993689
>>> random.random()
0.36589352300294087
>>> random.random()
0.9102553345613595
也可以在规定的范围内选择一个随机整数:
>>> random.randint(1, 10)
3
>>> random.randint(1, 10)
9
>>> random.randint(1, 10)
8
还能够从一个序列中随机地选取一项,以及打乱元素:
>>> random.choice(['Life of Brian', 'Holy Grail', 'Meaning of Life'])
'Holy Grail'
>>> random.choice(['Life of Brian', 'Holy Grail', 'Meaning of Life'])
'Meaning of Life'
>>>
>>> suits = ['hearts', 'clubs', 'diamonds', 'spades']
>>> random.shuffle(suits)
>>> suits
['clubs', 'diamonds', 'spades', 'hearts']
>>> random.shuffle(suits)
>>> suits
['spades', 'clubs', 'hearts', 'diamonds']
其他数值类型
小数类型
小数对象的名称是Decimal
,它们有固定的位数和小数点,它的精度是固定的。
基础知识
用浮点数来进行运算容易丢失精度:
>>> 0.1 + 0.2
0.30000000000000004
使用了小数对象,则结果更精确:
>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.2')
Decimal('0.3')
我们用decimal
模块中的Decimal
函数创建小数对象,传入一个表示小数的字符串。当不同精度的小数组合,则按照精确度最高的小数给出结果:
>>> Decimal('0.1') + Decimal('0.10')
Decimal('0.20')
我们可以从一个浮点数创建小数对象,但它会产生默认庞大的小数位数。
>>> Decimal(0.1) + Decimal(0.2)
Decimal('0.3000000000000000166533453694')
设置全局小数精度
默认的小数精度是:小数部分保留28位有效数字。
>>> import decimal
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal('0.1428571428571428571428571429')
我们可以这么修改显示小数部分有效数字的位数:
>>> decimal.getcontext().prec = 4
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal('0.1429')
>>> decimal.Decimal(0.1) + decimal.Decimal(0.2) - decimal.Decimal(0.3)
Decimal('1.110E-17')
小数上下文管理器
我们可以把特定的精度要求放在一个环境里,在环境外还是默认精度:
>>> 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')
分数类型
分数基础知识
向上面的Decimal
,分数也需要导入模块fraction
,并通过两个整数(第一个是分子,第二个是分母)作为参数构造:
>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> y = Fraction(4, 6)
>>> x
Fraction(1, 3)
>>> y
Fraction(2, 3)
>>> print(y)
2/3
我们可以把分数用于表达式运算符:
>>> x + y
Fraction(1, 1)
>>> x - y
Fraction(-1, 3)
>>> x * y
Fraction(2, 9)
分数和小数的数值精度
分数和小数的运算都比浮点数更直观、准确。而且,分数会自动简化结果。
>>> Fraction(6, 12)
Fraction(1, 2)
分数转换和混用类型
浮点数有一个方法,可以将本身转化为分子分母组成的元组。
>>> (2.5).as_integer_ratio()
(5, 2)
然后转换为分数,这里*
表示将元组的每个元素分别作为函数的参数。
>>> f = 2.5
>>> z = Fraction(*f.as_integer_ratio())
>>> z
Fraction(5, 2)
>>> x = Fraction(1, 3)
>>> float(x)
0.3333333333333333
>>> float(z)
2.5
注意到,float()
可以将分数类型转换为浮点数。Fraction
也有一种方法将浮点数转换为分数。
>>> Fraction.from_float(1.75)
Fraction(7, 4)
分数加整数的结果是分数,分数加浮点数的结果是浮点数:
>>> x = Fraction(1, 3)
>>> x + 1
Fraction(4, 3)
>>> x + 1.0
1.3333333333333333
但是,如果浮点数不能精确地表达某些实数(如1/3),那么转换为分数结果不精确。
>>> 1.0 / 3
0.3333333333333333
>>> (1.0 / 3).as_integer_ratio()
(6004799503160661, 18014398509481984)
我们可以限制分母地最大值来简化结果:
>>> a = Fraction(*(1.0 / 3).as_integer_ratio())
>>> a
Fraction(6004799503160661, 18014398509481984)
>>> a.limit_denominator(10)
Fraction(1, 3)
集合
集合的性质与数学中集合的性质一致。如:无序、没有重复元素。
集合基础知识
我们可以用一个序列或可迭代对象来创建集合:
>>> x = set('abcde')
>>> y = set('bdxyz')
>>> x
{'d', 'a', 'c', 'b', 'e'}
集合的运算:
>>> x - y # 差集
{'a', 'c', 'e'}
>>> x | y # 并集
{'d', 'x', 'z', 'a', 'c', 'b', 'e', 'y'}
>>> x & y # 交集
{'d', 'b'}
>>> x ^ y # 对称差
{'e', 'z', 'a', 'x', 'y', 'c'}
>>> x > y, x < y # 是否为子集
(False, False)
集合成员测试in
:元素是否在集合/列表/字符串内。
>>> 'e' in x
True
>>> 'e' in 'Camelot', 22 in [11, 22, 33]
(True, True)
集合操作还提供了与这些操作对应的方法:
>>> z = x.intersection(y) # 交集
>>> z
{'d', 'b'}
>>> z.add('SPAM') # 加入新的元素
>>> z
{'d', 'b', 'SPAM'}
>>> z.update(set(['X', 'Y'])) # 与集合合并(并集)
>>> z
{'Y', 'X', 'b', 'd', 'SPAM'}
>>> z.remove('b') # 删除一个元素
>>> z
{'Y', 'X', 'd', 'SPAM'}
集合可以用于len
、for
操作,但不适用于索引、分片。
集合操作对应的方法对任何可迭代类型有效:
>>> S = set([1, 2, 3])
>>> S | set([3, 4])
{1, 2, 3, 4}
>>> S | [3, 4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'set' and 'list'
>>> S.union([3, 4])
{1, 2, 3, 4}
>>> S.union((3, 4))
{1, 2, 3, 4}
新旧版本的集合字面量
对于集合S = {1, 2, 3}
,新版本(Python2.7,Python3.X)这么显示:
>>> S
{1, 2, 3}
旧版本(Python 2.X)这么显示:
>>> S
set([1, 2, 3])
空集表示(因此字面量{}
的类型是字典):
>>> S - {1, 2, 3}
set()
不可变性限制与冻结集合
集合只能包含不变的对象类型。
>>> S = {1.23}
>>> S.add([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> S.add({'a': 1})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> S.add((1, 2, 3))
>>> S
{1.23, (1, 2, 3)}
我们可以调用内置的frozenset
,这种类型的集合不可改变。
Python 3.X和Python 2.7中的集合推导
集合推导会运行一个循环并在每次迭代时收集一个表达式的结果,通过一个循环变量访问当前的迭代值以用于集合表达式中。
>>> {x ** 2 for x in [1, 2, 3, 4]}
{16, 1, 4, 9}
在这个表达式中,循环部分在右侧,集合体表达式在左侧(x ** 2)
。意思是:对于列表[1, 2, 3, 4]
中的每一个x
,给出包含x
平方的一个新集合。推导式也适用于迭代其他类型的对象(如字符串)。
为什么使用集合
去除集合体中的重复
>>> L = [1, 1, 4, 5, 1, 4]
>>> set(L)
{1, 4, 5}
>>> list(set(L))
[1, 4, 5]
提取可迭代对象的差异,借助集合完成顺序无关的等价性测试。
>>> set(dir(bytes)) - set(dir(bytearray))
{'__bytes__', '__getnewargs__'}
>>> set(dir(bytearray)) - set(dir(bytes))
{'insert', 'reverse', '__delitem__', 'copy', 'extend', '__iadd__', 'pop', 'append', '__imul__', 'clear', 'remove', '__alloc__', '__setitem__'}
>>> L1 = [1, 5, 2, 4, 3]
>>> L2 = [5, 1 ,2, 3, 4]
>>> L1 == L2
False
>>> set(L1) == set(L2)
True
布尔型
Python通过判断表达式的真假得到布尔值。
布尔型实际上是特殊的整数类型,True
对应1,False
对应0。
>>> True + 4
5
>>> False + 4
4
数值扩展
NumPy可以处理更复杂的数值类型,如向量、矩阵。