文章目录
一、数字
1.1 数据的取整
使用round()
方法对浮点数取整到固定的小数位,如下:
round(1.27, 1)
1.3
round(1.5, 0)
2.0
round(2.5, 0)
2.0
需要注意,当某个值正好等于两个整数间的一半时,取整操作会取到离该值最近的偶数
当然,round()
方法中的ndigits
可以为负数,这种情况下会相应地取整到十位、百位、千位,如下:
round(1568941, -1)
1568940
round(1568941, -2)
1568900
当然可以用format()
方法进行格式化操作,如下:(具体的将在1.3节中讲到)
x = 1.2567
print(format(x, '0.2f'))
1.26
print('value is {:0.3f}'.format(x))
value is 1.257
1.2 精确的小数计算
浮点数存在一定的缺陷,其无法精确地表达出所有的十进制小数位,如下:
a = 2.1
b = 4.2
a + b
6.300000000000001
(a + b) == 6.3
False
这种误差来自于底层CPU的浮点运算单元和IEEE 754浮点数算术标准的一种“特性”。
如果希望避免上面这种情况,得到更高的精度(并且愿意牺牲一些性能),可以使用decimal
模块,如下:
from decimal import Decimal
a = Decimal('2.1')
b = Decimal('4.2')
a + b
Decimal('6.3')
(a + b) == Decimal('6.3')
True
虽然上面的操作看上去十分奇怪,但是decimal
模块能够以任何你想的方式来工作,其支持所有常见的数字操作,包括数字的四舍五入,如下:
from decimal import Decimal, localcontext
a = Decimal('1.3')
b = Decimal('1.7')
print(a / b)
0.7647058823529411764705882353
with localcontext() as ctx:
ctx.prec = 3
print(a / b)
0.765
此外,我们还要注意非常大与非常小的数字相加时产生的一种忽略性问题,如下:
nums = [1.23e+18, 1, -1.23e+18]
sum(nums)
0.0
很明显,总和应该为1,这种情况可以用math.fsum()
来更加精准地实现,如下:
nums = [1.23e+18, 1, -1.23e+18]
import math
math.fsum(nums)
1.0
1.3 数值的格式化输出
其实这在前面以及前几章的内容中中多少涉及到一点,这里更加具体了,使用内建的format()
方法,如下:
x = 1234.56789
format(x, '0.2f')
'1234.57'
# 左对齐
format(x, >'10.1f')
' 1234.6'
# 右对齐
format(x, '<10.1f')
'1234.6 '
# 居中
format(x, '^10.1f')
' 1234.6 '
# 千分位
format(x, ',')
'1,234.56789'
format(x, '0,.1f')
'1,234.6'
如果箱采用科学计数法,把f
改为e
或者E
,如下:
format(x, 'e')
'1.234568e+03'
format(x, '0.2E')
'1.23E+03'
在Python中,可以使用%
操作符来对数值进行格式化处理,如下:
'%0.2f' % x
'1234.57'
'%-10.2f' % x
'1234.57 '
1.4 数值中的二进制、八进制、十六进制数
普通数字可以通过bin()
、oct()
、hex()
方法转化成相应的二进制、八进制、十六进制数,如下:
x = 1234
bin(x)
'0b10011010010'
x = 1234
oct(x)
'0o2322'
hex(x)
'0x4d2'
可以观察到前面有前缀
0b
、0o
、0x
,分别表示了二进制、八进制、十六进制。
若不希望出现上面的前缀,使用format()
函数,如下:
x = 1234
format(x, 'b')
'10011010010'
format(x, 'o')
'2322'
format(-x, 'x')
'-4d2'
如果要进行反向操作,即将字符串格式的进制数转化为普通数字,如下:
int('4d2', 16)
1234
int('10011010010', 2)
1234
1.5 字节串中打包和解包大整数
假设我们有一个16个元素的字节串,其中保存了一个128位的整数,如下:
data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004'
len(data)
16
使用int.to_bytes()
转化为整数,如下:
int.from_bytes(data, 'little')
69120565665751139577663547927094891008
int.from_bytes(data, 'big')
94522842520747284487117727783387188
可以通过int.to_bytes()
方法进行反向操作,如下:
x = 94522842520747284487117727783387188
x.to_bytes(16, 'big')
b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004'
1.6 数值中的复数运算
复数可以通过complex(real, img)
函数来指定,也可以在浮点数后面加上j
来指定,如下:
a = complex(2, 4)
b = 3 - 5j
print('a ', a)
a (2+4j)
print('b ', b)
b (3-5j)
a.real
2.0
a.imag
4.0
a.conjugate()
(2-4j)
此外,常见的运算符都适用于复数,如下:
a = 2 - 4j
b = 3 - 5j
a + b
(5-1j)
a * b
(26+2j)
a / b
(-0.4117647058823529+0.6470588235294118j)
abs(a)
4.47213595499958
如果要执行关于复数的更复杂的运算函数操作,如正余弦等,可以用cmath
库,如下:
a = 2 - 4j
import cmath
cmath.sin(a)
(24.83130584894638+11.356612711218174j)
cmath.cos(a)
(-11.36423470640106+24.814651485634187j)
cmath.exp(a)
(-4.829809383269385+5.5920560936409816j)
若想通过开方产生复数,标准情况下不能正常进行,需要通过cmath
库,如下:
import math
math.sqrt(-1)
ValueError: math domain error
import cmath
cmath.sqrt(-1)
1j
1.7 数值中的分数运算
使用fractions
库来建立分数,如下:
from fractions import Fraction
a = Fraction(5, 4)
b = Fraction(7, 16)
print(a + b)
27/16
print(a * b)
35/64
当然可以对分数进行很多其他的操作,如下:
from fractions import Fraction
a = Fraction(5, 4)
a.numerator # 分子
5
a.denominator # 分母
4
float(a)
1.25
# 限制分母
print(a.limit_denominator(3))
4/3
# float to 分数
x = 3.75
y = Fraction(*x.as_integer_ratio())
y
Fraction(15, 4)
1.8 数值中的无穷大和NAN
无穷大、负无穷大以及NAN(Not a number)可以通过float
创建,如下:
a = float('inf')
b = float('-inf')
c = float('nan')
要检测是否出现了这些值,可以用math.isinf()
和math.isnan()
函数,如下:
math.isinf(a)
True
math.isnan(c)
True
无穷大值的运算,如下:
a = float('inf')
a + 45
inf
a * 10
inf
10 / a
0.0
在某些特定情况下会产生NaN的结果,如下:
a = float('inf')
b = float('-inf')
a / a
nan
a + b
nan
NaN
会通过所有的操作进行传播,且不会引发任何异常,如下:
a = float('nan')
a + 23
nan
a / 2
nan
a * 2
nan
math.sqrt(a)
nan
有关于NaN
,有一个微妙的特性,其不能比较,如下:
a = float('nan')
b = float('nan')
a == b
False
a is b
False
因此,唯一安全的检测其是否为NaN
的办法为math.isnan()
。
1.9 大型数组的计算
在这一节中,我们重点介绍Numpy
库的数组操作。首先,我们看一下Python list
的操作,如下:
x = [1, 2, 3, 4]
y = [5, 6, 7, 8]
x * 2
[1, 2, 3, 4, 1, 2, 3, 4]
x + 10
TypeError: can only concatenate list (not "int") to list
x + y
[1, 2, 3, 4, 5, 6, 7, 8]
可以发现,list
中这些运算都偏向格式叠加,而在Numpy.array
中则偏向于相应位置的数值运算,如下:
import numpy as np
ax = np.array([1, 2, 3, 4])
ay = np.array([5, 6, 7, 8])
ax * 2
array([2, 4, 6, 8])
ax + 10
array([11, 12, 13, 14])
ax + ay
array([ 6, 8, 10, 12])
ax * ay
array([ 5, 12, 21, 32])
若相对numpy.array
使用其他形式的计算,可以如下:
import numpy as np
ax = np.array([1, 2, 3, 4])
def f(x):
return 3*x**2 - 2*x + 7
f(ax)
array([ 8, 15, 28, 47])
np.sqrt(ax)
array([1. , 1.41421356, 1.73205081, 2. ])
np.cos(ax)
array([ 0.54030231, -0.41614684, -0.9899925 , -0.65364362])
可以使用np.zeros()
构建一个指定形状的数组,如下:
import numpy as np
grid = np.zeros(shape=(100,100), dtype=float)
grid
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
当然list
也可以构建一个指定大小的列表,如下:
a = [[0] * 10]*5
a
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
接下来是对于Numpy.array
的一些基础操作,比如选择行列等,如下:
a = np.array([[1,2,3,4], [5,6,7,8]])
# 选择第0行
a[0]
array([1, 2, 3, 4])
# 选择第0列
a[:, 0]
array([1, 5])
# 选择一个sub-region并改变它
a[0:1, 0:1] += 10
array([[11, 2, 3, 4],
[ 5, 6, 7, 8]])
# 设置条件变化
np.where(a<10, a, 100)
array([[100, 2, 3, 4],
[ 5, 6, 7, 8]])
# 整列相加
a + [100, 100, 100, 100]
array([[111, 102, 103, 104],
[105, 106, 107, 108]])
1.10 矩阵和线性代数计算
使用numpy.matrix
来构造矩阵对象,先介绍其一些基础的矩阵操作,如下:
import numpy as np
m = np.matrix([[1,-2,3],[0,4,5],[7,8,-9]])
m
matrix([[ 1, -2, 3],
[ 0, 4, 5],
[ 7, 8, -9]])
# 矩阵转置
m.T
matrix([[ 1, 0, 7],
[-2, 4, 8],
[ 3, 5, -9]])
# 矩阵的逆
m.I
matrix([[ 0.33043478, -0.02608696, 0.09565217],
[-0.15217391, 0.13043478, 0.02173913],
[ 0.12173913, 0.09565217, -0.0173913 ]])
# 矩阵乘法
v = np.matrix([[2],[3],[4]])
m * v
matrix([[ 8],
[32],
[ 2]])
1.11 数值序列的随机选择
random
库是进行各种随机操作的方便之选,如下:
import random
values = [1,2,3,4,5,6]
# 随机选择1个元素
random.choice(values)
4
# 随机选择N个元素(N=2)
random.sample(values, 2)
[5, 2]
# 随机打乱元素的顺序
random.shuffle(values)
values
[5, 2, 1, 6, 3, 4]
其他的,可以使用random
产生随机的数,如下:
# 产生随机的整数
random.randint(0, 10)
9
# 0-1之间产生随机的浮点数
random.random()
0.13686015760081516
# 产生由N个随机比特位所表示的整数
random.getrandbits(100)
1210582918078660346171090150836
可以通过random.seed()
函数来修改初始的种子值,如下:
random.seed() # seed based on system time or os.urandom()
random.seed(10) # seed based on iteger given
random.seed(b'byte') # seed based on byte data
二、时间与日期
2.1 时间换算
可以利用datetime
模块来完成不同时间单位的换算,要表示一个时间间隔,可以创建一个timedelta
实例,如下:
from datetime import timedelta
a = timedelta(days=2, hours=6)
b = timedelta(hours=4.5)
c = a + b
c.days
2
c.seconds / 3600
10.5
c.total_seconds()
210600.0
如果需要表示特定的日期与时间,可以创建datetime
实例并使用标准的数学运算符来操作,如下:
from datetime import timedelta
from datetime import datetime
a = datetime(2021, 9, 30)
print(a + timedelta(days=10))
2021-10-10 00:00:00
b = datetime(2021, 1, 1)
d = a - b
d
datetime.timedelta(days=272)
d.days
272
now = datetime.today()
print(now)
2022-09-30 23:23:58.648476
要注意,
datetime
模块在计算之间的天数时是可以正确地处理闰年的。
datetime
模块存在一定的缺陷,其不能处理关于月份的问题,但这个时候,可以使用dateutil.relativedelta()
函数来完成,如下:
from datetime import timedelta
from datetime import datetime
a = datetime(2021, 9, 30)
a + timedelta(months=1)
TypeError: 'months' is an invalid keyword argument for __new__()
from dateutil.relativedelta import relativedelta
d = a + relativedelta(months=1)
print(d)
2021-10-30 00:00:00
relativedelta(d, a)
relativedelta(months=+1)
2.2 计算上周5的日期
我们希望有一个通用的方法能找出一周中上一次出现某天时的日期,比方说上周周五是几月几号,如下:
from datetime import datetime, timedelta
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
def get_previous_byday(dayname, startdate=None):
if startdate == None:
startdate = datetime.today()
day_num = startdate.weekday() # 得到startdate的在周期中的序号,起始为0。(若今天为周五,则day_num=4)
day_num_target = weekdays.index(dayname) # 得到startdate的在周期中的序号,起始为0。(若今天为周一,则day_num=0)
days_ago = (7 + day_num - day_num_target) % 7
if days_ago == 0:
days_ago = 7
return startdate - timedelta(days=days_ago)
datetime.today()
datetime.datetime(2022, 10, 1, 8, 39, 42, 430106)
get_previous_byday('Monday')
datetime.datetime(2022, 9, 26, 8, 39, 42, 697596)
当然可以使用dateutil
库来完成同样的计算,如下:
from datetime import datetime
from dateutil.relativedelta import relativedelta
from dateutil.rrule import *
d = datetime.now()
print(d)
2022-10-01 08:44:31.201295
# Next Friday
print(d + relativedelta(weekday=FR))
2022-10-07 08:44:31.201295
# Last Friday
print(d + relativedelta(weekday=FR(-1)))
2022-09-30 08:44:31.201295
2.3 将字符串转化为日期
可以使用datetime
库来处理将输入字符串转化为日期的问题,如下:
from datetime import datetime
text = '2012-09-20'
y = datetime.strptime(text, '%Y-%m-%d')
y
datetime.datetime(2012, 9, 20, 0, 0)
其中
%Y
表示年份的位置,%m
表示月份的位置,%d
表示天的日子。
可以将日期格式datetime
对象生成为文本格式,使用datetime.strftime()
方法,如下:
datetime.strftime(datetime.now(), '%A %B %d, %Y')
'Saturday October 01, 2022'
2.4 处理涉及到时区的日期问题
对于几乎任何涉及到时区的问题时,都用pytz
模块来实现,如下:
from datetime import datetime
from pytz import timezone
d = datetime(2012, 12, 21, 9, 30, 0)
# Localize the date for Chicago
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
总结
查漏补缺~
参考:《Python cookbook 中文版》[美]David Beazley&Brian K. Jones 著