想对浮点数执行指定精度的舍入运算,可使用内置的 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浮点数的一个普遍问题是它们不能精确地表示十进制数。即使是最简单的数学运算也会产生小的误差(使用 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))需要将【单个数字】格式化后输出,并控制数字的位数、对齐、千位分隔符和其他的细节时,可使用内置的 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() 函数是更好的选择。
下面介绍转换 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 的第二参数指定“进制”看不太懂0.0
接下来介绍 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下面介绍正无穷、负无穷 & 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() """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/4limit_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涉及到数组的重量级运算时可使用 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 (注意这里的参数必须和数组变量名一致)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)从一个序列中随机抽取若干元素 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() 可生成正态分布的随机数。
# 为展示上面两个函数的效果, 需要结合可视化, 这里先挖个坑咯~为执行不同时间单位的转换和计算,可使用 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)如何获取上一个周五出现的日期?
# 返回上一个星期 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))) # 这里传递的参数是什么含义?想要遍历当前月份中的每一天,需要一个计算这个日期范围的高效方法。
这样的遍历需要先构造一个包含所有日期的列表。为此可先计算出开始日期 & 结束日期,然后在步进时使用 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 月的日历字符串形式的日期时间 与 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 好得多。
看不懂0.0以后再啃