【Python Cookbook】第三章 数字、日期和时间


一、数字

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'

可以观察到前面有前缀0b0o0x,分别表示了二进制、八进制、十六进制。

若不希望出现上面的前缀,使用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 著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prymce-Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值