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