python3 cookbook 第三章
前言
第三章讲的是数字日期和时间,在地铁上看得七七八八了,我想跟上一章一样,快速过一遍得了,感觉没啥好讲的。
数字的四舍五入
老生常谈的一个问题,感觉 cookbook
上的解释也有点模糊,我推荐直接看知乎上的这篇文章:为什么你需要少看垃圾博客以及如何在Python里精确地四舍五入(标题真的没问题嗷),下面是搬运的重点部分。
Python 3里面,round
对小数的精确度采用了四舍六入五成双
的方式。
如果你写过大学物理的实验报告,那么你应该会记得老师讲过,直接使用四舍五入,最后的结果可能会偏高。所以需要使用奇进偶舍
的处理方法。
例如对于一个小数a.bcd
,需要精确到小数点后两位,那么就要看小数点后第三位:
- 如果d小于5,直接舍去
- 如果d大于5,直接进位
- 如果d等于5:
1. d后面没有数据,且c为偶数,那么不进位,保留c
2. d后面没有数据,且c为奇数,那么进位,c变成(c + 1)
3. 如果d后面还有非0数字,例如实际上小数为a.bcdef,此时一定要进位,c变成(c + 1)
关于奇进偶舍,有兴趣的同学可以在维基百科搜索这两个词条:数值修约和奇进偶舍。
所以, round()
给出的结果如果与你设想的不一样,那么你需要考虑两个原因:
- 你的这个小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如
1.115
,它的小数点后第三位实际上是4
,当然会被舍去。 - 如果你的这个小数在计算机中能被精确表示,那么,
round
采用的进位机制是奇进偶舍
,所以这取决于你要保留的那一位,它是奇数还是偶数,以及它的下一位后面还有没有数据。
关于一个小数到底能不能被精确储存,这篇回答中有提到:
从数学上看,一个既约分数(有理数)n/d 要表示为 B 进制数,如果 d 的所有素因子都整除 B,就说明存在一个整数 k,使得分母 d 整除 B^k——比如 q * d = B^k,于是此时有 n/d = n / (B^k / q) = nq / B^k,也就是说 n/d 可以使用至多 k 位数的 B 进制小数有限表示。
最后是自己参照评论里的一个列子写的函数,遵循四舍五入:
d = 1.115
print(round(d, 2))
print('{:.22f}'.format(d))
def myround(par, l):
return (par*(10**l)+0.5)//1/(10**l)
print(myround(d, 2))
----
1.11
1.1149999999999999911182
1.12
执行精确的浮点数运算
浮点数的一个普遍问题是它们并不能精确的表示十进制数。 并且,即使是最简单的数学运算也会产生小的误差,比如:
>>> a = 4.2
>>> b = 2.1
>>> a + b
6.300000000000001
>>> (a + b) == 6.3
False
这些错误是由底层CPU和IEEE 754标准通过自己的浮点单位去执行算术时的特征。 由于Python的浮点数据类型使用底层表示存储数据,因此你没办法去避免这样的误差。
如果你想更加精确(并能容忍一定的性能损耗),你可以使用 decimal
模块,具体的使用方法在第一个超链接文章里面有,用之前务必要看。
数字的格式化输出
讲的是format
,随便看看。
二八十六进制整数
为了将整数转换为二进制、八进制或十六进制的文本串, 可以分别使用 bin()
, oct()
或 hex()
函数:
>>> x = 1234
>>> bin(x)
'0b10011010010'
>>> oct(x)
'0o2322'
>>> hex(x)
'0x4d2'
>>> format(x, 'b')
'10011010010'
>>> format(x, 'o')
'2322'
>>> format(x, 'x')
'4d2'
为了以不同的进制转换整数字符串,简单的使用带有进制的 int()
函数即可:
>>> int('4d2', 16)
1234
>>> int('10011010010', 2)
1234
字节到大整数的打包与解包
这个应该不太常用,反正我是不会也没用到过。
假设你的程序需要处理一个拥有128位长的16个元素的字节字符串。比如:
data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004'
为了将bytes解析为整数,使用 int.from_bytes()
方法,并像下面这样指定字节顺序:
>>> len(data)
16
>>> 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'
>>> x.to_bytes(16, 'little')
b'4\x00#\x00\x01\xef\xcd\x00\xab\x90x\x00V4\x12\x00'
复数的数学运算
这个真没必要。
无穷大与NaN
Python并没有特殊的语法来表示这些特殊的浮点值,但是可以使用 float()
来创建它们。比如:
>>> a = float('inf')
>>> b = float('-inf')
>>> c = float('nan')
>>> a
inf
>>> b
-inf
>>> c
nan
>>> math.isinf(a)
True
>>> math.isnan(c)
True
无穷大数在执行数学计算的时候会传播,数学知识?NaN值会在所有操作中传播,而不会产生异常,NaN值的一个特别的地方时它们之间的比较操作总是返回False,Pandas用多了自然就知道了。
分数运算
一般用不到,用到了再看 fractions
模块。
大型数组运算
import numpy as np
矩阵与线性代数运算
>>> 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]])
>>> # Return transpose
>>> m.T
matrix([[ 1, 0, 7],
[-2, 4, 8],
[ 3, 5, -9]])
>>> # Return inverse
>>> m.I
matrix([[ 0.33043478, -0.02608696, 0.09565217],
[-0.15217391, 0.13043478, 0.02173913],
[ 0.12173913, 0.09565217, -0.0173913 ]])
>>> # Create a vector and multiply
>>> v = np.matrix([[2],[3],[4]])
>>> v
matrix([[2],
[3],
[4]])
>>> m * v
matrix([[ 8],
[32],
[ 2]])
随机选择
random
模块有大量的函数用来产生随机数和随机选择元素。 比如,要想从一个序列中随机的抽取一个元素,可以使用 random.choice()
:
>>> import random
>>> values = [1, 2, 3, 4, 5, 6]
>>> random.choice(values)
2
>>> random.choice(values)
3
为了提取出N个不同元素的样本用来做进一步的操作,可以使用 random.sample()
:
>>> random.sample(values, 2)
[4, 3]
>>> random.sample(values, 3)
[4, 3, 1]
如果你仅仅只是想打乱序列中元素的顺序,可以使用 random.shuffle()
:
>>> random.shuffle(values)
>>> values
[2, 4, 6, 5, 3, 1]
>>> random.shuffle(values)
>>> values
[3, 5, 2, 1, 6, 4]
生成随机数:
# 整数
>>> random.randint(0,10)
5
# 0到1的浮点数
>>> random.random()
0.9406677561675867
# 指定区间的浮点数
>>> random.uniform(1,100)
66.19053400179031
# N位随机位(二进制)的整数
>>> random.getrandbits(200)
335837000776573622800628485064121869519521710558559406913275
每次调用random()
会生成不同的值,在一个非常的的周期之后数字才会重复。这对于生成唯一值或变化的值很有用,不过有些情况下可能需要提供相同的数据集,从而以不同的方式处理。对此,一种方法是使用一个程序来生成随机数,并保存这些随机数,以便通过一个单独的方式另行处理。不过对于量很大的数据来说可能并不实用,所以random
包含了一个seed()
函数,用来初始化伪随机数生成器,使它能生成一个期望的值集。
种子(seed
)值会控制生成伪随机数所用公式产生的第一个值,由于公式是确定的,改变种子也就设置了整个要生成的序列。seed()
的参数可以是任意可散列对象。默认会使用平台特定的随机源(如果有的话os.urandom()
)。否则,会使用当前时间(system time
)。
for i in range(3):
random.seed(1)
print(random.uniform(1, 100))
# 14.30206016712772
# 14.30206016712772
# 14.30206016712772
此外,random
模块使用的伪随机算法的内部状态可以保存,并用于控制后续各轮生成的随机数。继续生成随机数之前恢复前一个状态,这会减少有之前输入得到重复的值或值序列的可能性。getstate()
函数会返回一些数据,以后可以用setstate()
利用这些数据重新初始化伪随机数生成器。
基本的日期与时间转换
日期的转换和计算:
>>> from datetime import timedelta
>>> a = timedelta(days=2, hours=6)
>>> b = timedelta(hours=4.5)
>>> c = a + b
>>> c.days
2
>>> c.seconds
37800
>>> c.seconds / 3600
10.5
>>> c.total_seconds() / 3600
58.5
如果你想表示指定的日期和时间,先创建一个 datetime
实例然后使用标准的数学运算来操作它们。比如:
>>> from datetime import datetime
>>> a = datetime(2012, 9, 23)
>>> print(a + timedelta(days=10))
2012-10-03 00:00:00
>>>
>>> b = datetime(2012, 12, 21)
>>> d = b - a
>>> d.days
89
>>> now = datetime.today()
>>> print(now)
2012-12-21 14:54:43.094063
>>> print(now + timedelta(minutes=10))
2012-12-21 15:04:43.094063
计算上一个周五的日期
我的:
from datetime import datetime, timedelta
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
def f(dayname, start_day=None):
start_day = datetime.now() if start_day == None else start_day
current_weekday = start_day.weekday()
try:
target_weekday = weekdays.index(dayname)
except ValueError as e:
print(f'{dayname} is not a correct weekday name')
days = current_weekday+7-target_weekday if target_weekday>= current_weekday else current_weekday-target_weekday
temp = timedelta(days=days)
target_day = start_day-temp
return target_day
书上的:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
Topic: 最后的周五
Desc :
"""
from datetime import datetime, timedelta
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
def get_previous_byday(dayname, start_date=None):
if start_date is None:
start_date = datetime.today()
day_num = start_date.weekday()
day_num_target = weekdays.index(dayname)
days_ago = (7 + day_num - day_num_target) % 7
if days_ago == 0:
days_ago = 7
target_date = start_date - timedelta(days=days_ago)
return target_date
计算当前月份的日期范围
比较easy,学习下replace()
和calendar.monthrange()
的用法:
from datetime import datetime, date, timedelta
import calendar
def get_month_range(start_date=None):
if start_date is None:
start_date = date.today().replace(day=1)
_, 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)
字符串转换为日期
使用strptime
函数会比较方便:
text = '2020-5-12'
y = datetime.strptime(text, '%Y-%m-%d')
x = datetime.now()
z = x-y
print(z)
# 2 days, 17:52:07.063600
但是性能很差,如果格式固定可以采用split()
加datetime()
来转化,听说速度快乐7倍多。
结合时区的日期操作
书里面用的是pytz
模块,我看得有点晕,加上datetime.datetime.now().hour==18
了,我该溜咯。
写在后面
今天一天啥也没做,把第三章拿下了,芜湖~