文章目录
- Chap 3 数字日期和时间
- 3.1 数字的四舍五入 --- round(value, ndigits)
- 3.2 执行精确的浮点数运算 --- Decimal(str) / localcontext() / math.fsum()
- 3.3 数字的格式化输出 --- format() on floats & Decimal Obj
- 3.4 2 / 8 / 16 进制整数 --- bin() / oct() / hex() & format() / int()
- 3.5 字节到大整数的打包与解包 --- ???
- 3.6 复数的数学运算 --- complex() / 1 + 1j / .real / .imag / .conjugate() / cmath
- 3.7 无穷大与 NaN --- float() / math.isinf() / math.isnan()
- 3.8 分数运算 --- Fraction(p, q) / fraction.limit_denominator(n) / some_float.as_integer_ratio()
- 3.9 大型数组运算 --- numpy.ndarray 各种特性及通用函数, np.where()
- 3.10 矩阵与线性代数运算 --- np.matrix(), .T, .I, .det(), .eigvals(), .solve(m, v)
- 3.11 随机选择 --- random.choice(), .sample(), .shuffle(), .randint(), .random(), .seed()
- 3.12 基本的日期与时间转换 --- timedelta, datetime, 其间的加减法, today, dateutil.relativedelta
- 3.13 计算最后一个周五的日期 --- datetime 模块的应用, dateutil 模块的应用
- 3.14 计算当前月份的日期范围 --- datetime 的应用, calendar 简介, 处理日期时间的 Generator
- 3.15 字符串转换为日期 --- datetime.strptime(), datetime.strftime()
- 3.16 结合时区的日期操作 --- ???
Chap 3 数字日期和时间
3.1 数字的四舍五入 — round(value, ndigits)
想对浮点数执行指定精度的舍入运算,可使用内置的 round(value, ndigits) 函数:
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
# 当一个值刚好在两个边界的中间时, round 函数返回离它最近的【偶数】:
print(round(1.5, 0)) # 2.0
print(round(2.5, 0)) # 2.0
# 传给 round() 函数的 ndigits 参数可以为负, 此时舍入运算会作用在十位、百位、千位等上面:
a = 1627731
print(round(a, -1)) # 1627730 舍去个位
print(round(a, -2)) # 1627700 舍去十位
print(round(a, -3)) # 1628000 舍去百位
若只是想简单地输出【一定宽度的数】,则只需在格式化时指定精度即可,无需使用 round 函数:
x = 1.23456
print(format(x, '0.2f')) # 1.23
print(format(x, '0.3f')) # 1.235 而非 1.234 (同 round 函数的规则)
print('value is {:0.3f}'.format(x))
不要试着用 round 函数来 “修正” 运算结果:在计算的时候会有一点点小的误差,但这些小的误差是能被理解与容忍的。如果不能允许这样的小误差,就得考虑使用 decimal 模块了。
a, b = 2.1, 4.2
c = a + b
print(c) # 6.300000000000001, 这里无需对结果做 round 来得到 6.3
3.2 执行精确的浮点数运算 — Decimal(str) / localcontext() / math.fsum()
浮点数的一个普遍问题是它们不能精确地表示十进制数。即使是最简单的数学运算也会产生小的误差(使用 Python 的浮点数据类型时,没办法避免这样的误差):
b, a = 2.1, 4.2
c = a + b
print(c) # 6.300000000000001
print(c == 6.3) # False
若需要对浮点数执行精确的计算操作,并且不希望有任何小误差出现(还要能容忍一定的性能损耗),则可使用 decimal 模块。
Decimal 对象会像普通浮点数一样地工作,它支持所有的常用数学运算。当在 print & format 函数中使用它们时,看起来跟普通数字没什么两样。
from decimal import Decimal
a = Decimal('4.2') # 注意这里参数为 str
b = Decimal('2.1')
c = a + b
print(c) # 6.3
print(c == Decimal('6.3')) # True
print(format(c, '>10.5f')) # ‘ 6.30000’
decimal 模块的一个主要特征是允许你控制计算的每一方面,包括数字位数和四舍五入运算。为此,你先得创建一个本地上下文并更改它的设置:
from decimal import localcontext
a, b = Decimal('1.3'), Decimal('1.7')
print(a/b) # 默认精度为 30
with localcontext() as ctx:
ctx.prec = 50 # 指定精度为 50
print(a/b)
Remark:在做科学领域的大多数运算时,使用普通浮点类型就已足够。① 在真实世界中很少会要求精确到普通浮点数能提供的 17 位精度。② 原生的浮点数计算要快得多,特别是在执行大量运算的时候。
# 仍要注意在 “大数和小数的加法运算中” 的误差:
import math
nums = [1.23e+18, 1, -1.23e+18]
# 1. 直接求和, 结果是 0 而不是 1, 这显然有误
print(sum(nums))
# 2. 使用 fsum 函数解决上面的问题
print(math.fsum(nums))
3.3 数字的格式化输出 — format() on floats & Decimal Obj
需要将【单个数字】格式化后输出,并控制数字的位数、对齐、千位分隔符和其他的细节时,可使用内置的 format() 函数:
x = 1234.56789
# 1. 控制位数 & 对齐
print('[' + format(x, '0.2f') + ']') # [1234.57]
print('[' + format(x, '>10.1f') + ']') # [ 1234.6]
print('[' + format(x, '<10.1f') + ']') # [1234.6 ]
print('[' + format(x, '^10.1f') + ']') # [ 1234.6 ]
# 2. 附上千位分隔符
print(format(x, ','))
print(format(x, '0,.1f'))
# 3. 想使用科学计数法, 只需将 f 改成 e / E (取决于指数输出的大小写形式)
print(format(x, 'e')) # 1.234568e+03
print(format(x, '0.2E')) # 1.23E+03
# 4. 下面的例子同时指定了宽度 & 精度 (居中对齐, 长度20, 附上千位分隔符, 保留两位小数)
print('The value is [{:^20,.2f}]'.format(x))
上面演示的技术也适用于 Decimal 数字对象。另外,不推荐使用 (%) 来格式化数字,format() 函数是更好的选择。
3.4 2 / 8 / 16 进制整数 — bin() / oct() / hex() & format() / int()
下面介绍转换 or 输出使用二进制,八进制 or 十六进制表示的整数。
# 1. 为将整数转换为 2/8/16 进制的【字符串】, 可分别使用 bin, oct, hex 函数:
x = 1234
print(bin(x)) # 二进制
print(oct(x)) # 八进制
print(hex(x)) # 十六进制
# 2. 若不想输出 0b, 0o, 0x の前缀时可使用 format() 函数:
print(format(x, 'b'))
print(format(x, 'o'))
print(format(x, 'x'))
# 3. 负整数的情况:
x = -1234
print(format(x, 'b'))
print(format(x, 'o'))
print(format(x, 'x'))
# 4. 将 2, 8, 16 进制的【数字字符串】转换为十进制整数:
print(int('0b10011010010', 2))
print(int('0o2322', 8))
print(int('0x4d2', 16)) # int 的第二参数指定“进制”
3.5 字节到大整数的打包与解包 — ???
看不太懂0.0
3.6 复数的数学运算 — complex() / 1 + 1j / .real / .imag / .conjugate() / cmath
接下来介绍 Python 中关于复数的基础知识:
# 1. 使用 Complex Numbers 的两种方案:
a = complex(2, 4)
b = 3 - 5j
print(a, b)
# 2. 获取 Complex Numbers 的实部, 虚部 & 共轭复数:
print(a.real, a.imag) # 实部 & 虚部
print(a.conjugate()) # 共轭复数
# 3. 复数的数学运算:
print(a + b)
print(a * b)
print(a / b)
print(abs(a)) # 复数の模
# 4. 使用复变函数 (正/余弦, 指数函数等):
import cmath
print(cmath.sin(a))
print(cmath.cos(a))
print(cmath.exp(a))
# 5. 使用 numpy 构造复数数组并在其上执行各种操作:
import numpy as np
a = np.array([2 + 3j, 4 + 5j, 6 - 7j, 8 + 9j])
print(a)
print(a + 2)
print(np.sin(a))
# 6. Python 的标准数学函数不能产生复数值:
import math
print(math.sqrt(-1)) # ValueError: math domain error
# 7. 上述问题改进如下:
print(cmath.sqrt(-1)) # 1j
3.7 无穷大与 NaN — float() / math.isinf() / math.isnan()
下面介绍正无穷、负无穷 & NaN(非数字) 的浮点数:
# 1. 使用 float() 来创建正无穷, 负无穷 & NaN(非数字):
a = float('inf')
b = float('-inf')
c = float('nan')
print(a, b, c)
# 2. 使用 math.isinf() & math.isnan() 函数测试这些值的存在性 :
import math
print(math.isinf(a))
print(math.isinf(b))
print(math.isnan(c))
# 3. 无穷大在数学计算中会传播:
print(45 + float('-inf'))
print(10 * float('-inf'))
print(233 / float('-inf')) # -0.0 (注意这个负号)
# 4. 有些操作是未定义的并且会返回一个 NaN (和数学中的未定型一致):
print(float('inf') / float('-inf'))
print(float('inf') + float('-inf'))
# 5. NaN 会在【所有数学计算】中传播而不会产生异常:
print(23 + float('nan')) # nan
print(float('nan') / 2) # nan
print(16 * float('nan')) # nan
print(math.sqrt(float('nan'))) # nan
# 6. NaN 有一个特点, 它们之间的比较操作总是返回 False:
print(float('nan') == float('nan')) # False
print(float('nan') is float('nan')) # False
""" 由于这个原因, 测试一个 NaN 唯一安全的方法就是使用 math.isnan() """
3.8 分数运算 — Fraction(p, q) / fraction.limit_denominator(n) / some_float.as_integer_ratio()
fractions 模块可被用来执行包含分数的数学运算:
from fractions import Fraction
# 1. 创建 Fraction 实例
a, b = Fraction(5, 4), Fraction(7, 16)
print(f'a = {a}; b = {b}')
print(f'a + b = {a + b}')
print(f'a * b = {a * b}')
# 2. Getting numerator/denominator
c = a * b
print(c)
print(c.numerator, c.denominator) # 分子 & 分母
# 3. Fraction to Float
print(float(c))
# 4. Limiting the denominator of a value
d = c.limit_denominator(8) # see below
print(f'c = {c}; d = {d}; difference = {float(c - d)}')
# 5. Float to Fraction
x = 3.75
print(x.as_integer_ratio()) # (15, 4) 将 float 数值转化为相应分子分母所成的 tuple
y = Fraction(*x.as_integer_ratio())
print(y) # 15/4
limit_denominator(max_denominator=1000000) 返回一个 Fraction 使其值最接近 self (即调用此方法的 Fraction 实例) 且分母不大于 max_denominator。此方法适用于找出给定浮点数的有理近似值:
pi = Fraction('3.1415926535897932') # 另一种实例化 Fraction 对象的方法
print(pi) # 7853981633974483/2500000000000000
pi_q = pi.limit_denominator(1000) # 限制分母不超过 1000
print(pi_q, float(pi_q)) # 355/113 3.1415929203539825
3.9 大型数组运算 — numpy.ndarray 各种特性及通用函数, np.where()
涉及到数组的重量级运算时可使用 NumPy 库,其一个主要特征是它会给 Python 提供一个 (较标准 Python 列表更适合做数学运算的) 数组对象。
# 1. Python lists
x = [1, 2, 3, 4]
y = [5, 6, 7, 8]
print(x * 2) # [1, 2, 3, 4, 1, 2, 3, 4]
print(x + y) # [1, 2, 3, 4, 5, 6, 7, 8]
# print(x + 10) # TypeError: can only concatenate list (not "int") to list
# 2. Numpy arrays
import numpy as np
ax = np.array([1, 2, 3, 4])
ay = np.array([5, 6, 7, 8])
print(ax * 2) # [2 4 6 8]
print(ax + ay) # [ 6 8 10 12]
print(ax + 10) # [11 12 13 14] 注意, 这种运算对 list 是不允许的
print(ax * ay) # [ 5 12 21 32]
print(ax ** ay) # 对应元素间的幂运算
NumPy 中的标量运算 (如 ax * 2 or ax + 10 ) 会作用在每个元素上;当两个操作数都是数组时执行对应位置上的元素间的运算,并最终生成一个新数组。
# 1. 计算多项式值的例子:
def f(x):
""" 定义一个多项式 """
return 3*(x**2) - 2*x + 7
print(f(ax)) # [ 8 15 28 47] 对数组中每个元素同时计算了 f(x) 所定义的 polynomial 的值
# 2. Numpy 提供的通用函数, 可用于代替 math 模块中的对应函数:
""" 使用这些通用函数要比循环数组并使用 math 模块中的函数执行计算要快的多。 """
print(np.sqrt(ax)) # 对每个元素开方
print(np.cos(ax)) # 求每个元素的余弦值
# 3. Numpy 数组较 list 的优势之一, 创建巨大的二维数组:
grid = np.zeros(shape=(10000, 10000), dtype=float) # 10,000 by 10,000 2d-array
print(grid)
print(grid + 10) # 每个元素执行 + 10 的运算
print(np.sin(grid + 10)) # 对每个元素 + 10 后求 sin 的值
Numpy 多维数组的索引功能值得注意:
# >>>>>> Indexing
# -----------------------------------------------------------------------------------------
a = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]]) # 2d-array with size=(3, 4)
print(a)
# 1. Select row 1
print(a[1])
# 2. Select column 1
print(a[:, 1])
# 3. Select a subregion and change it (注意 3 是取不到的)
a[1:3, 1:3] += 10
print(a)
# >>>>>> Broadcast a row vector across an operation on all rows
# -----------------------------------------------------------------------------------------
print(a + [100, 101, 102, 103])
# 将一个 (1d) 行向量加到 2d 数组上, 效果是该行向量被加到 2d 数组中每个行向量上了, 这类似 scalar + 1d array 的情况, 那么对所有低维对象 + 高维对象, 这种规律是否都被满足?
# 注意这里 a 本身没有变化, 因为执行计算会生成一个新的 array
print(a)
# >>>>>> Conditional assignment on an array
# -----------------------------------------------------------------------------------------
print(np.where(a < 10, a, 10))
# 在数组 a 中小于 10 的元素输出其自身, 否则输出 10 (注意这里的参数必须和数组变量名一致)
3.10 矩阵与线性代数运算 — np.matrix(), .T, .I, .det(), .eigvals(), .solve(m, v)
NumPy 通过矩阵对象来执行【矩阵 & 线性代数运算】,如矩阵乘法,求行列式值,解线性方程组等:
import numpy as np
# 1. 实例化一个 3X3 矩阵
m = np.matrix([[1, -2, 3],
[0, 4, 5],
[7, 8, -9]])
print(m)
# 2. Transpose
print(m.T)
# 3. Inverse
print(m.I)
# 4. 实例化一个 3X1 矩阵 (列向量)
v = np.matrix([[2],
[3],
[4]])
# 5. Matrix Multiplication
print(m * v)
import numpy.linalg as la # Linear Algebra 方面的计算通过 linalg 来实现
# 6. Determinant
print(la.det(m))
# 7. Eigenvalues
print(la.eigvals(m))
# 8. Solve for x in (mx = v)
x = la.solve(m, v)
print(x)
# 9. check solution
print(m * x == v)
3.11 随机选择 — random.choice(), .sample(), .shuffle(), .randint(), .random(), .seed()
从一个序列中随机抽取若干元素 or 生成几个随机数可通过 random 模块实现:
import random
# 1.想从一个序列中随机抽取一个元素, 可使用 random.choice():
values = [x for x in range(1, 6)]
print(random.choice(values))
# 2. 为提取 N 个“不同元素”的样本用于进一步操作, 可使用 random.sample():
print(random.sample(values, 3))
# 3. 想打乱序列中元素的顺序, 可使用 random.shuffle():
random.shuffle(values)
print(values) # 注意到序列本身被改变了
# 4. 生成随机“整数”可使用 random.randint():
print(random.randint(0, 10))
print(random.randint(10, 100))
# 5. 为生成 0 到 1 范围内“均匀分布”的浮点数, 可使用 random.random():
print(random.random())
# 6. 可通过 random.seed() 函数修改初始化种子:
random.seed() # Seed based on system time or os.urandom()
random.seed(12345) # Seed based on integer given
""" 因为生成的是伪随机数, 有时需要复现随机数, 有时则需要不同的随机, 这时 random.seed() 就有用了 """
另外,random.uniform() 可生成均匀分布的随机数;random.gauss() 可生成正态分布的随机数。
# 为展示上面两个函数的效果, 需要结合可视化, 这里先挖个坑咯~
3.12 基本的日期与时间转换 — timedelta, datetime, 其间的加减法, today, dateutil.relativedelta
为执行不同时间单位的转换和计算,可使用 datetime 模块:
# 1. 时间段:
from datetime import timedelta
a = timedelta(days=2, hours=0.5) # 创建时间段对象 timedelta
b = timedelta(hours=1.5)
c = a + b # timedelta 对象可做加减法
print(c.days, c.seconds) # 天数, 秒数
print(c.seconds / 3600) # 秒数 to 小时数
print(c.total_seconds(), c.total_seconds() / 3600) # 总秒数, 总小时数
# 2. 表示指定的日期 & 时间:
from datetime import datetime
a = datetime(2020, 9, 3) # 创建日期时间对象 datetime
b = datetime(2020, 10, 1)
print(a, b)
c = a + timedelta(days=10) # 日期时间对象 +/- 时间段对象
d = b - a # datetime 对象相减得到一个 timedelta 对象 (不支持相加!)
print(c)
print(d)
print(d.days)
now = datetime.today() # 当前日期时间
print(now)
print(now + timedelta(minutes=10)) # 当前日期时间对象 +/- timedelta
# 3. 注意 datetime 会自动处理闰年:
a = datetime(2012, 3, 1)
b = datetime(2012, 2, 28)
print(a - b) # 2 days, 0:00:00 (可见2012为闰年)
c = datetime(2013, 3, 1)
d = datetime(2013, 2, 28)
print(c - d) # 1 day, 0:00:00 (可见2013非闰年)
# 4. 更复杂的日期操作 ———— dateutil 模块中的 relativedelta() 方法:
from dateutil.relativedelta import relativedelta
a = datetime(2020, 7, 23)
# print(a + timedelta(months=1))
# 结果为 TypeError: 'months' is an invalid keyword argument for __new__()
""" timedelta 对象无法实现“增加一个月的时间段”, 这样的功能可通过 relativedelta 对象实现 """
print(a + relativedelta(months=+4)) # 这个 (+) 似乎不写也行
# 5. 同样时间间隔的不同表示: timedelta VS relativedelta
a = datetime(2020, 7, 23)
b = datetime(2020, 12, 24)
d = b - a
print(d) # 154 days, 0:00:00
d = relativedelta(b, a)
print(d) # relativedelta(months=+5, days=+1)
print(d.months, d.days)
3.13 计算最后一个周五的日期 — datetime 模块的应用, dateutil 模块的应用
如何获取上一个周五出现的日期?
# 返回上一个星期 X 的具体日期 (如星期五)
from datetime import datetime, timedelta, date
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
def get_previous_byday(dayname, start_date=None):
# 1.若不指定起始日期, 则以当前日期为起始日期
if start_date is None:
start_date = date.today()
# 2.获取起始日期的星期数, 0 ~ 6, 如 Sunday 对应 6
day_num = start_date.weekday()
# 3.确定目标星期数在 weekdays 列表中的索引
day_num_target = weekdays.index(dayname)
# 4.⭐⭐⭐⭐⭐⭐确定目标星期数还要经过几天才能到达起始星期数⭐⭐⭐⭐⭐⭐
days_ago = (7 + day_num - day_num_target) % 7
# 5.若今天的星期数同目标星期数恰好相同, 则上个目标星期数必为7天前
if days_ago == 0:
days_ago = 7
# 6.从起始日期减去相应的天数, 获得目标星期数的具体日期
target_date = start_date - timedelta(days=days_ago)
return target_date
# 测试上面的函数
print(date.today()) # 以当前日期为参照
x = get_previous_byday('Monday') # 上个周一的具体日期
y = get_previous_byday('Tuesday') # 上个周二的具体日期
z = get_previous_byday('Friday') # 上个周五的具体日期
print(x, y, z, sep='\n')
w = get_previous_byday('Sunday', date(2020, 8, 1)) # 2020-08-01 前的一个周日的日期
print(w)
dateutil 模块更适合执行大量日期计算:
from datetime import date
from dateutil.relativedelta import relativedelta
from dateutil.rrule import *
d = date.today() # 以当前日期为参照
print(d)
# 2.1 Next Friday
print(d + relativedelta(weekday=FR)) # 这里传递的参数是什么含义?
# 2.2 Last Friday
print(d + relativedelta(weekday=FR(-1))) # 这里传递的参数是什么含义?
3.14 计算当前月份的日期范围 — datetime 的应用, calendar 简介, 处理日期时间的 Generator
想要遍历当前月份中的每一天,需要一个计算这个日期范围的高效方法。
这样的遍历需要先构造一个包含所有日期的列表。为此可先计算出开始日期 & 结束日期,然后在步进时使用 datetime.timedelta 对象递增日期变量:
from datetime import datetime, date, timedelta
import calendar
def get_month_range(start_date=None):
if start_date is None:
# 获取当前日期(不含时间)后, 将 day 替换为 1 (即变成本月 1 号)
start_date = date.today().replace(day=1) # replace 也可以指定 year & month
# calendar.monthrange(year, month) 返回指定年月的第一天是周几(Monday 以 0 表示) & 指定年月共有几天
_, 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
# 1. 定义“步长”为 1 天
delta_day = timedelta(days=1)
# 2. 调用函数, 获取起始日期 & 终止日期
first_day, last_day = get_month_range()
# 3. 通过 while 开始遍历
while first_day < last_day: # 这里使用 (<) 而不是 (<=), 否则将出现下月 1 号
print(first_day)
first_day += delta_day
可以为日期的迭代创建一个 Generator 如下:
def date_range(start, stop, step):
while start < stop:
yield start
start += step
# 下面使用此 Generator:
for d in date_range(datetime(2020, 9, 1), datetime(2020, 10, 1), timedelta(hours=12)):
# 注意 step 不一定得是以天为单位
print(d)
上面结构简单的 Generator 能够这么有效,还得归功于 Python 中日期与时间能够使用标准数学 & 比较操作符来进行运算。
calendar 模块有个很实用的功能:
print(calendar.calendar(2020)) # 2020 年的日历
print(calendar.month(2020, 8)) # 2020 年 8 月的日历
3.15 字符串转换为日期 — datetime.strptime(), datetime.strftime()
字符串形式的日期时间 与 datetime 对象相互转换:
# 1. 将【字符串形式的日期时间】转化为【相应的 datetime 对象】:
from datetime import datetime
text = '2020-09-1'
y = datetime.strptime(text, '%Y-%m-%d') # 通过 strptime 实现 str 到 datetime
z = datetime.today()
diff = z - y # 现在可以进行 datetime 对象的运算了
print(type(y), y)
print(diff)
""" datetime.strptime() 方法支持很多的格式化代码, 如 %Y 代表 4 位数年份, %m 代表两位数月份。"""
# 2. 将【datetime 对象】转化为 【好康的 str 对象】:
z = datetime(2020, 7, 28, 12, 21)
print(type(z), z) # 2020-07-28 12:21:00
pretty_z = datetime.strftime(z, '%A %B %d, %Y') # 通过 strftime 实现 datetime 到 str
print(type(pretty_z), pretty_z) # Tuesday July 28, 2020
'''
需要注意的是,strptime() 的性能很差,特定情况下,下面的解析函数性能上会好得多
'''
def parse_ymd(s):
"""
:param s: 日期字符串
:return: 日期时间对象
"""
year_s, month_s, day_s = s.split('-')
return datetime(int(year_s), int(month_s), int(day_s))
假如要处理大量的日期格式为 YYYY-MM-DD 的字符串,采用上面的函数效率远比 datetime.strptime 好得多。
3.16 结合时区的日期操作 — ???
看不懂0.0以后再啃