第三章 数字日期和时间
3.1 数字的四舍五入
简单的舍入运算使用 round() 函数即可:
>>> round(1.2345, 1)
1.2
>>> round(1.2345, 3)
1.234
>>> round(31.2345, 0)
31.0
>>> round(31.2345, -1)
30.0
round() 的第二位参数 ndigits,表示精度,可以为负。
当被舍入小数刚好在两个边界中间时,舍入结果根据前一位的值遵循奇进偶舍的原则。
python 的浮点数存储规则会导致微小的误差存在,所以有时肉眼看到的值为 5 时,并非边界中间。
>>> round(4.5)
4
>>> round(4.45, 1)
4.5
>>> from decimal import Decimal
>>> Decimal(4.5)
Decimal('4.5')
>>> Decimal(4.45)
Decimal('4.45000000000000017763568394002504646778106689453125')
3.2 执行精确的浮点数运算
python 的浮点数据类型使用底层表示,受底层浮点单位的影响,浮点运算有时会产生不可避免的误差。
>>> 6.8 * 9
61.199999999999996
decimal 模块可以避免这种误差。
>>> from decimal import Decimal
>>> a = Decimal('6.8')
>>> a * 9
Decimal('61.2')
Decimal 初始化的对象是浮点数时,能看到该浮点数的实际值。小数点后17位后才出现误差,实际使用中很少涉及这么大的精度,而且使用这种原生浮点运算速度会快。
>>> Decimal(1.1)
Decimal('1.100000000000000088817841970012523233890533447265625')
>>> 1.22e+18 == (1.22e+18 + 1)
True
>>> 1.22e+18 - 1.22e+18 + 1
1.0
>>> 1.22e+18 - (1.22e+18 + 1)
0.0
3.3 数字的格式化输出
内置的 format() 函数能控制位数、对齐、千位分隔符。格式化输入规则如下:
[[填充符]对齐方式][符号][#][0][宽度][千位分隔符][小数位数][类型]
- 对齐方式:’<’,左对齐;’>’,右对齐;’^’,居中对齐;’=’,对齐到正负号后面,只能对数值使用。
- 符号:’+’,整数显示正号,负数显示负号;’-’,只有负数显示负号(默认);’ ',正数前面会留一个空格,负数前面是负号。
- 宽度:正整数。
- 千位分隔符:’,’ 或 ‘_’。
- 小数位数:正整数。
3.4 二八十六进制整数
bin(),oct(),hex() 分别把整数转换为二进制、八进制、十六进制的字符串,输出结果带有前缀 0b,0o,0x。
不想要前缀,可以使用 format() 函数,使用 ’b‘,‘o’,‘x’ 指定类型。
3.5 字节到大整数的打包与解包
字节字符串到整数:
>>> data = bytes('一二三', encoding='utf8')
>>> int.from_bytes(data, 'big')
4219152526395654322313
>>> int.from_bytes(data, 'little')
2540526866430202788068
大整数转换为字节字符串:
>>> x = 4219152526395654322313
>>> x.to_bytes(16, 'big')
b'\x00\x00\x00\x00\x00\x00\x00\xe4\xb8\x80\xe4\xba\x8c\xe4\xb8\x89'
>>> x.to_bytes(16, 'little')
b'\x89\xb8\xe4\x8c\xba\xe4\x80\xb8\xe4\x00\x00\x00\x00\x00\x00\x00'
很大的整数打包为字节字符串时,可以使用 bit_length() 方法来确定位数。
x.to_bytes(x.bit_length(), 'big')
3.6 复数的数学运算
复数可以用 complex() 或带有后缀 j 的浮点数来指定。
real 和 imag 属性用来获取其实部和虚部,conjugate() 方法获取其共轭复数。
加减乘除等常见运算可以直接操作,但是正弦、余弦、平方根等运算需要使用 cmath 模块。
>>> import cmath
>>> cmath.cos(3+5j)
(-73.46729221264526-10.471557674805572j)
>>> cmath.sqrt(-1) # math.sqrt(-1) 无法计算
1j
3.7 无穷大与 NaN
这两种特殊的值能通过 float() 创建:
>>> a = float('inf')
>>> a
inf
>>> b = float('-inf')
-inf
>>> c = float('nan')
>>> c
nan
测试这些值的存在要使用 math 模块的 isinf() 和 isnan() 来判定:
>>> math.isinf(a)
True
无穷大在数学计算时会传播,
>>> a + 1
inf
>>> 100 / a
0.0
有些未定义的运算返回 nan:
>>> a - a
nan
>>> a / a
nan
nan 值在所有操作中都会传播,并且不会异常:
>>> c + 6
nan
>>> c / 6
nan
>>> c * 6
nan
nan 的特别的地方是比较操作:
>>> c == c
False
>>> c is c
True
>>> c is float('nan')
False
因此,判定 nan 值的唯一方法只能是 math.isnan() 方法。
3.8 分数运算
fractions 模块能用来执行分数运算。
>>> from fractions import Fraction
>>> a = Fraction(3, 4)
>>> a
Fraction(3, 4)
>>> print(a)
3/4
>>> a.numerator # 分子
3
>>> a.denominator # 分母
4
>>> (a*a).limit_denominator(8) # 限制分母大小
Fraction(4, 7)
3.9 大型数组运算
numpy 库能创建数组对象,其运算和列表不同。
- 标量运算会作用在数组中的每一个元素上;
- 数组之间的运算作用在对等位置的元素上;
- 提供了类似 math 模块的通用函数(平方根、三角函数等),计算起来比迭代使用 math 的函数快;
- 容易建立大型数组;支持索引操作;支持条件选择。
3.10 矩阵与线性代数运算
numpy 库的 matrix 对象类似数组对象,但是遵循线性代数的计算规则。
import numpy as np
>>> a = np.array([[1,1,1],[2,2,2],[3,3,3]])
>>> b = np.array([[4],[4],[4]])
>>> a * b
array([[ 4, 4, 4],
[ 8, 8, 8],
[12, 12, 12]])
>>> a = np.matrix(a)
>>> b = np.matrix(b)
>>> a * b
matrix([[12],
[24],
[36]])
使用 a.I 获得矩阵 a 的逆矩阵,a.T 获得矩阵 a 的转置矩阵。
在 numpy 的 linalg 中有更多的操作矩阵的函数。
3.11 随机选择
random 模块
- random.choice(序列),从序列中随机取一个元素;
- random.sample(序列, 个数),从总体序列中随机取指定个数的样本;
- random.shuffle(序列),打乱序列中元素顺序;
- random.randint(a, b),生成区间 [a, b] 内的随机整数;
- random.random(),生成区间 [0, 1) 之间的随机浮点数;
- random.seed(),修改初始化种子;
- 用于根据正态分布、高斯分布等生成随机数;
random 模块不要用在和密码学相关的程序中。
3.12 基本的日期与时间转换
datetime 模块的 timedelta 可以完成天、时、分、秒的单位换算:
>>> from datetime import timedelta
>>> a = timedelta(hours=3.2)
>>> a
datetime.timedelta(seconds=11520)
timedelta 实例可以和 datetime 实例做加减操作。
对于更复杂的时间操作,可以使用 dateutil 模块
3.13 计算最后一个周五的日期
from datetime import datetime, timedelta
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday','Friday', 'Saturday', 'Sunday']
def get_previous_byday(dayname, start_date=None):
if start_date is None:
start_date = datetime.today()
# 开始日期的索引,目标日期的索引
day_num = start_date.weekday()
day_num_target = weekdays.index(dayname)
# 目标日期在多少天前
days_ago = (7 + day_num - day_num_target) % 7
if days_ago == 0:
days_ago = 7
target_date = start_date - timedelta(days=days_ago)
return target_date
使用第三方包 dateutil 能更简单地实现:
>>> from datetime import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from dateutil.rrule import *
>>> d = datetime.now()
>>> d + relativedelta(weekday=FR) # 下一个周五
datetime.datetime(2020, 4, 10, 18, 45, 17, 665903)
>>> d + relativedelta(weekday=FR(-1)) # 上一个周五
datetime.datetime(2020, 4, 3, 18, 45, 17, 665903)
3.14 计算当前月份的日期范围
calendar 模块的 monthrange(year, month),返回输入月份的第一天的星期和该月天数。
datetime 和 date 实例的 replace() 方法可以返回指定某项时间的 datetime 或 date 实例。
from datetime import datetime, date, timedelta
import calendar
def get_month_range(start_date=None):
# 获得指定日期所在月份的开始日期和结束日期
if start_date is None:
start_date = date.today().replace(day=1)
_, days_in_month = calendar.monthrange(start_date.year,start_date.month)
end_date = start_date + timedelta(days=days_in_month)
return (start_date, end_date)
3.15 字符串转换为日期
datetime 模块的 strptime(text, format) 方法可以把日期格式的字符串转为 datetime 实例。
strptime 方法效率很低,如果要处理大量格式统一的日期字符串,自己写一个方法转换效率更高。
3.16 结合时区的日期操作
pytz 模块。
(读完了,作者备注了一句 pytz 可能过时了-_-)