第三章:数字日期和时间

3.1 数字的四舍五入

round(value, ndigits) 函数

1
2
3
4
5
print(round(1.23, 1))  # 1.2
print(round(1.27, 1)) # 1.3
print(round(-1.27, 1)) # -1.3
print(round(1.25361,3)) # 1.254
print(round(1627735,-1)) # 1627740

3.2 执行精确的浮点数运算

浮点数的一个普遍问题是它们并不能精确的表示十进制数。

1
2
3
4
5
a = 4.2
b = 2.1

print(a+b) # 6.300000000000001
print(6.3==a+b) # False

这些错误是由底层 CPU 和 IEEE 754 标准通过自己的浮点单位去执行算术时的特征。由于 Python 的浮点数据类型使用底层表示存储数据,因此你没办法去避免这样的误差。

使用 decimal 模块:

1
2
3
4
5
6
7
8
9
from decimal import Decimal

# 注意要传入str
a = Decimal('4.2')
b = Decimal('2.1')

print(a+b) # 6.3
print(6.3 == a+b) # False
print(Decimal('6.3') == a+b) # False

用字符串来表示数字

使用decimal获取精确到小数位N位:

1
2
3
4
5
6
7
8
9
10
from decimal import localcontext,Decimal

a = Decimal('1.3')
b = Decimal('1.7')

print(a/b) # 0.7647058823529411764705882353

with localcontext() as ctx:
ctx.prec = 50
print(a/b) # 0.76470588235294117647058823529411764705882352941176

3.3 数字的格式化输出

控制数字的位数、对齐、千位分隔符和其他的细节

1
2
3
4
5
6
7
8
x = 1234.56789

print(format(x,'.2f')) # 1234.57
print(format(x,'>10.2f')) # 1234.57

# 添加千位符
print(format(x,',')) # 1,234.56789
print(format(x,',.2f')) # 1,234.57

format的格式为:'[<>^]?width[,]?(.digits)?'

3.4 二八十六进制整数

bin() , oct() , hex() 函数

也可以使用format来转化

1
2
3
4
5
6
7
8
9
10
x = 1234

print(bin(x)) # 0b10011010010
print(oct(x)) # 0o2322
print(hex(x)) # 0x4d2

# 也可以使用format转化
print(format(x,'b')) # 10011010010
print(format(x,'o')) # 2322
print(format(x,'x')) # 4d2

3.5 字节到大整数的打包与解包

  • 有一个字节字符串并想将它解压成一个整数。
  • 需要将一个大整数转换为一个字节字符串。

为了将 bytes 解析为整数,使用 int.from_bytes() 方法
为了将一个大整数转换为一个字节字符串,使用 int.to_bytes() 方法

res = int.from_bytes(x) :

  • 把bytes类型的变量x,转化为十进制整数,并存入res中。
  • int.from_bytes(bytes, byteorder, *, signed=False)
    byteorder主要有两种:’big’和’little’;signed=True表示需要考虑符号位。
    big代表正常顺序,little反之
1
2
3
4
5
6
7
8
9
10
data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004'

print(len(data)) # 16

print(int.from_bytes(data,'little')) # 69120565665751139577663547927094891008
print(int.from_bytes(data,'big')) # 94522842520747284487117727783387188

x = 94522842520747284487117727783387188
print(x.to_bytes(16,'big')) # b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004'
print(x.to_bytes(16,'little')) # b'4\x00#\x00\x01\xef\xcd\x00\xab\x90x\x00V4\x12\x00'

字节顺序规则 (little 或 big) 指定了构建整数时的字节的低位高位排列方式。

1
2
3
4
x = 0x01020304

print(x.to_bytes(4,'big')) # b'\x01\x02\x03\x04'
print(x.to_bytes(4,'little')) # b'\x04\x03\x02\x01'

3.6 复数的数学运算

使用函数 complex(real, imag) 或者是带有后缀 j 的浮点数来指定。

1
2
3
4
5
6
7
8
9
a = complex(2,4)
b = 3-5j

print(a) # (2+4j)
print(b) # (3-5j)

print(a.real,a.imag) # 2.0 4.0
# 共轭复数
print(a.conjugate()) # (2-4j)

3.7 无穷大与 NaN

1
2
3
4
5
6
7
8
9
import math

a = float('inf')
b = float('-inf')
c = float('nan')

print(math.isinf(a)) # True
print(math.isinf(b)) # True
print(math.isnan(c)) # True

float('inf'),float('-inf'),flaot('nan')三者都会在运算中传播
而nan的等级更高,inf + nan == nan

1
2
3
4
5
6
7
8
9
import math

a = float('inf')
b = float('-inf')
c = float('nan')

print(a + 5) # inf
print(a + b) # nan
print(a + c) # nan

和js一样,nan之间的比较总是False,只能使用math.isnan来判断

1
2
3
4
5
6
7
import math

a = float('nan')
b = float('nan')

print(a == b) # False
print(a is b) # False

3.8 分数运算

fractions 模块可以被用来执行包含分数的数学运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
from fractions import Fraction

a = Fraction(5,4)
b = Fraction(7,16)

print(a.numerator,a.denominator) # 5 4
print(float(a)) # 1.25
print(a + b) # 27/16

# 将浮点转为分数
x = 3.75
y = Fraction(*x.as_integer_ratio())
print(y)# 15/4

3.9 大型数组运算

使用 NumPy

3.10 矩阵与线性代数运算

执行矩阵和线性代数运算,比如矩阵乘法、寻找行列式、求解线性方程组等等。

使用NumPy 库有一个矩阵对象

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
m = np.matrix([[1,-2,3],[0,4,5],[7,8,-9]])
print(m)
# [[ 1 -2 3]
# [ 0 4 5]
# [ 7 8 -9]]

print(m.T)
# [[ 1 0 7]
# [-2 4 8]
# [ 3 5 -9]]

3.11 随机选择

random 模块

1
2
3
4
5
6
7
8
9
import random

x = ['a','b','c','d','e']
# 选样本
print(random.sample(x,2)) # ['e', 'a']

# 获取 N 位随机位 (二进制) 的整数
print(random.getrandbits(200))
# 1600357600159928260096229373737394765980934110472789307213699

random 模块还包含基于均匀分布、高斯分布和其他分布的随机数生成函数。

  • random.uniform() 计算均匀分布随机数
  • random.gauss() 计算正态分布随机数。

3.12 基本的日期与时间转换

执行不同时间单位的转换和计算,请使用 datetime 模块

  • timedelta 实例 : 表示一个时间段
  • datetime 实例 : 表示指定的日期和时间
1
2
3
4
5
6
7
8
9
10
from datetime import timedelta,datetime

a = timedelta(days=2,hours=6)
b = timedelta(hours=.5)
c = a + b
print(c) # 2 days, 6:30:00
print(c.days,c.seconds) # 2 23400

d = datetime(2012, 3, 1)
print(d+a) # 2012-03-03 06:00:00

datetime会自动处理闰年

3.13 计算最后一个周五的日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

print(datetime.today()) # 2019-07-27 10:11:52.155536
print(get_previous_byday('Monday')) # 2019-07-22 10:11:52.155536
print(get_previous_byday('Tuesday')) # 2019-07-23 10:11:52.155536

当然,也有第三方包 python-dateutil。

3.14 计算当前月份的日期范围

在这样的日期上循环并需要事先构造一个包含所有日期的列表。你可以先计算出开始日期和结束日期,然后在你步进的时候使用 datetime.timedelta 对象递增这个日期变量即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import calendar
from datetime import datetime, date, timedelta

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)


a_day = timedelta(days=1)
first_day, last_day = get_month_range()
while first_day < last_day:
print(first_day)
first_day += a_day
# 2019-07-01
# 2019-07-02
# ...
# 2019-07-30
# 2019-07-31

3.15 字符串与日期互转

  • strptime() :按照特定时间格式将字符串转换(解析)为时间类型
    p ==> parse
  • strftim() :将时间格式化
    f ==> format
1
2
3
4
5
6
7
8
9
10
from datetime import datetime

text = '2012-09-20'
y = datetime.strptime(text,'%Y-%m-%d')
z = datetime.now()

diff = z- y
print(diff) # 2501 days, 10:25:45.608327

print(datetime.strftime(z,'%Y-%m-%d')) # 2019-07-27
  • strptime() 的性能要比你想象中的差很多,因为它是使用纯 Python 实现,并且必须处理所有的系统本地设置。

  • 如果你要在代码中需要解析大量的日期并且已经知道了日期字符串的确切格式,可以自己实现一套解析方案来获取更好的性能。

  • 比如,如果你已经知道所以日期格式是 YYYY-MM-DD ,你可以像下面这样实现一个解析函数:

    1
    2
    3
    4
    from datetime import datetime
    def parse_ymd(s):
    year_s, mon_s, day_s = s.split('-')
    return datetime(int(year_s), int(mon_s), int(day_s))

3.16 结合时区的日期操作

对几乎所有涉及到时区的问题,应该使用 pytz 模块

  • pytz 模块一个主要用途是将 datetime 库创建的简单日期对象本地化
  • 一旦日期被本地化了,它就可以转换为其他时区的时间了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from datetime import datetime
from pytz import timezone

d = datetime(2012, 12, 21, 9, 30, 0)
print(d)
# 2012-12-21 09:30:00

central = timezone('US/Central')
loc_d = central.localize(d)
print(loc_d)
# 2012-12-21 09:30:00-06:00

bang_d = loc_d.astimezone(timezone('Asia/Kolkata'))
print(bang_d)
# 2012-12-21 21:00:00+05:30