这篇文章将学习 Python 中数学运算、日期与时间操作的常见编码技巧。
数字的四舍五入
问题
想对浮点数执行指定精度的舍入运算。
解决方案
对于简单的舍入运算,可以使用内置的 round(value, ndigits):
- 如果一个值刚好在两个边界的中间,
round函数返回离它最近的偶数 - 传入
round()的ndigits参数可以是负数,此时舍入运算会作用在十位、百位、千位等上面 - 不要将舍入和格式化输出搞混淆了,如果只是简单地输出一定宽度的数,不需要使用
round()函数,而只需要在格式化的时候指定精度即可 - 同样,不要试着去舍入浮点值来
修正表面上看起来正确的问题。如果真的不能允许小误差,那么可以考虑使用decimal模块
示例
1 | round(1.23, 1) |
1 | round(1.5, 0) |
1 | a = 1627731 |
1 | x = 1.23456 |
1 | a = 2.1 |
执行精确的浮点数运算
问题
需要对浮点数执行精确的计算操作,并且不希望有任何小误差出现。
解决方案
浮点数的一个普遍问题是他们并不能精确的表示十进制数,并且即使是最简单的数学运算也会产生小的误差。如果你想更精确(并且能容忍一定的性能损耗),可以使用 decimal 模块:
decimal 模块的一个主要特征是允许你控制计算的每一方面:包括数字位数和四舍五入运算。为了这样做,你得先创建一个本地上下文并更改它的设置。
dedimal 模块主要涉及金融领域,此时哪怕一点点小的误差在计算过程中都是不允许的。decimal 模块为解决这类问题提供了方法。
示例
1 | a = 4.2 |
1 | from decimal import Decimal |
1 | from decimal import localcontext |
数字的格式化输出
问题
你需要将数字格式化后输出,并控制数字位数、对齐、千位分隔符和其他细节。
解决方案
格式化输出单个数字的时候,可以使用内置的 format() 函数。
- 同时指定宽度和精度的一般形式是
[<>^]?width[,]?(.digits)?,width和digits都是整数,?表示可选部分 - 以上格式也适用于字符串的 format 方法
- 对于浮点数的格式化输出,同样适用于
decimal模块中的Decimal数字对象 - 当指定数字的位数后,结果值会根据
round()函数同样的规则进行四舍五入后返回 - 仍然建议使用
format()来进行格式化,%操作符支持的特性不如format()
示例
1 | x = 1234.56789 |
1 | x = 1234.56789 |
二八十六进制整数
问题
你需要转换或输出使用二进制、八进制或十六进制的整数。
解决方案
- 为了将整数转换为二进制、八进制或十六进制,可以分别使用
bin()、oct()或者hex()函数 - 如果不想输出前缀,可以使用
format()函数 - 为了将不同进制转换整数字符串,可以使用带有进制的
int()函数即可
示例
1 | x = 1234 |
1 | int("0x4d2", 16) |
字节到大整数的打包与解包
问题
想把一个字节字符串转换为整数,或者想把一个大整数准换为字节字符串
解决方案
- 为了将 bytes 解析为整数,可以使用
int.from_bytes()方法,同时指定字节序 - 为了将一个大整数转换为一个字节序列,使用
int.to_bytes()方法,并指定字节数 - 如果需要的话,可以使用
int.bit_length()方法来决定需要多少位来存储一个整数值
示例
1 | data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004' |
1 | x = 0x01020304 |
1 | x = 523 ** 23 |
复数的数学运算
问题
需要对复数执行计算操作。
解决方案
- 复数可以使用函数
complex(real, imag)或者是带有后缀j的浮点数来指定 - 对应的实部、虚部和共轭复数可以很容易获取
- 所有常见的数学运算也可以工作
- 如果需要执行其他的复数函数,可以使用 cmath 模块
- Python 中的大部分与数学相关的模块都能处理复数,比如
numpy - Python 的标准数学函数不会产生复数值,如果想生成一个复数返回结果,需要使用
cmath模块
示例
1 | a = complex(2, 4) |
1 | import math |
无穷大与 NaN
问题
想创建或测试正无穷、负无穷或者 NaN(非数字)的浮点数。
解决方案
- Python 并没有特殊的语法来表示这些特殊的浮点值,但是可以使用
float()来创建它们 - 为了测试这些值的存在,使用
math.isinf()和math.isnan()函数来判断 - 无穷大数在执行数学计算的时候会传播
- NaN 值在所有操作中传播,而且不会产生异常
- NaN 值一个特别的地方是它们之间的比较操作总是返回 Flase。因此测试 NaN 值唯一安全的方法就是使用
math.isnan() - 某些操作未定义时,会返回 NaN 结果
示例
1 | a = float('inf') |
1 | import math |
1 | a + 45 |
分数运算
问题
需要在代码中执行分数运算。
解决方案
fractions 模块可以被用来执行包含分数的数学运算,直接使用分数可以减少手动转换为小数或浮点数的工作。
示例
1 | from fractions import Fraction |
大型数组运算
问题
需要在大数据集上执行计算。
解决方案
NumPy 是 Python 领域中很多科学与工程库的基础,同时也是被广泛使用的最大、最复杂的模块。涉及数组的重量级运算操作可以使用 NumPy 库。
- NumPy 库提供一个 NumPy 数组对象,相比于 Python 原生的数组,更适合用来做数学运算
- NumPy 库为数组操作提供了大量的通用函数,这些函数可以作为 math 模块中类似函数的替代
- NumPy 也扩展了 Python 列表的索引功能,特别是对于多维数组
示例
1 | x = [1, 2, 3, 4] |
1 | import numpy as np |
1 | np.sqrt(ax) |
1 | grid = np.zeros(shape=(10000, 10000), dtype=float) |
1 | a |
矩阵与线性代数运算
问题
你需要执行矩阵和线性代数运算。
解决方案
- Numpy 库提供一个矩阵对象。矩阵类似于数组对象,但是遵循线性代数的计算规则
- 可以在
Numpy.linalg子包中找到更多的操作函数
示例
1 | import numpy as np |
1 | import numpy.linalg |
随机选择
问题
想从一个序列中随机抽取若干个元素,或者想生成几个随机数。
解决方案
- random 模块有大量的函数来产生随机数和随机选择元素
- 想要从一个序列中随机抽取一个元素,可以使用
random.choice() - 随机抽取 N 个不同元素,可以使用
random.sample() - 如果只是想打乱序列中元素的顺序,可以使用
random.shuffle() - 生成随机整数,可以使用
random.randint() - 生成 0 到 1 范围内均匀分布的浮点数,使用
random.random() - 想要获取 N 位随机位(二进制)的整数,使用
random.getrandbits()
random 模块使用确定性算法来生成随机数,但是可以通过 random.seed() 修改初始化种子。random 模块还包含基于均匀分布、高斯分布额其他分布的随机数生成函数。
random 模块中的函数不应该用于密码学相关的程序中,如果确实需要类似功能,可以使用 ssl 模块中相应的函数。例如 ssl.RAND_bytes() 可以用来生成一个安全的随机字节序列。
示例
1 | import random |
1 | random.randint(0, 10) |
1 | random.seed() |
基本的日期与时间转换
问题
需要执行简单的日期与时间转换。
解决方案
- 为了执行不同时间单位的转换和计算,可以使用
datetime模块 - 为了表示时间段,可以使用其
timedelta实例 - 想表示指定的日期和时间,可以使用
datetime实例 datetime模块会自动处理闰年- 如果需要处理复杂的日期操作,比如处理时区、模糊时间范围、节假日计算等,可以考虑使用
dateutil模块
示例
1 | from datetime import timedelta |
1 | c = datetime(2012, 3, 1) |
计算上一个周五的日期
问题
需要一个通用方法来计算一周中某一天上一次出现的日期。
解决方案
- 可以通过 datetime 模块中的工具函数和类实现该功能
- 第三方包
python-dateutil直接提供了相关功能
示例
1 | from datetime import datetime, timedelta |
1 | from datetime import datetime |
计算当前月份的日期范围
问题
想要获取当前月份的的所有日期。
解决方案
- 可以首先计算出当前月份的开始日期和结束日期,然后使用
datetime.timedelta对象递增该日期 - 计算一个月份的第一天的日期,最简单的方法就是调用
date或datetime对象的replace()方法将 days 属性设置为 1 - 通过
calendar.monthrange()可以计算某个月的天数 - Python 中的日期和时间能够使用标准的数学和比较操作来进行运算
示例
1 | from datetime import datetime, date, timedelta |
字符串转换为日期
问题
需要将字符串转换为 datetime 对象,以方便执行日期时间计算操作。
解决方案
datetime.strptime()方法支持将字符串转换为 datetime 对象,其支持多种格式化代码datetime.strftime()方法可以将 datetime 对象转换为字符串strptime()的性能较差,有时候也可以自己实现解析方案,以获取更好的性能
示例
1 | from datetime import datetime |
1 | nice_z = datetime.strftime(z, '%A %B %d, %Y') |
1 | from datetime import datetime |
结合时区的日期操作
问题
在执行日期操作时,需要结合时区信息。
解决方案
- 几乎所有涉及到时区的问题,都应该使用 pytz 模块。它提供了 Olson 时区数据库,也是时区信息事实上的标准
pytz模块主要用途是将datetime库创建的简单日期对象本地化。一旦日期被本地化了之后,就可以转换为其他时区时间- 处理本地化日期的通常策略是先将所有日期转换为 UTC 时间,并用它来执行所有中间存储和操作。一旦转换为 UTC,就不用担心和夏令时相关的问题了。
- 在处理时区时,可以使用
ISO 3166国家代码作为关键字去查阅字典pytz.country_timezones获取时区信息
示例
1 | from datetime import datetime |
1 | print(local_d) |
1 | pytz.country_timezones('CN') |