python3 cookbook 笔记九

前言

第三章讲的是数字日期和时间,在地铁上看得七七八八了,我想跟上一章一样,快速过一遍得了,感觉没啥好讲的。

数字的四舍五入

老生常谈的一个问题,感觉 cookbook 上的解释也有点模糊,我推荐直接看知乎上的这篇文章:为什么你需要少看垃圾博客以及如何在Python里精确地四舍五入(标题真的没问题嗷),下面是搬运的重点部分。

Python 3里面,round 对小数的精确度采用了四舍六入五成双的方式。

如果你写过大学物理的实验报告,那么你应该会记得老师讲过,直接使用四舍五入,最后的结果可能会偏高。所以需要使用奇进偶舍的处理方法。

例如对于一个小数a.bcd,需要精确到小数点后两位,那么就要看小数点后第三位:

  1. 如果d小于5,直接舍去
  2. 如果d大于5,直接进位
  3. 如果d等于5:
    1. d后面没有数据,且c为偶数,那么不进位,保留c
    2. d后面没有数据,且c为奇数,那么进位,c变成(c + 1)
    3. 如果d后面还有非0数字,例如实际上小数为a.bcdef,此时一定要进位,c变成(c + 1)

关于奇进偶舍,有兴趣的同学可以在维基百科搜索这两个词条:数值修约和奇进偶舍。

所以, round() 给出的结果如果与你设想的不一样,那么你需要考虑两个原因:

  1. 你的这个小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如1.115,它的小数点后第三位实际上是4,当然会被舍去。
  2. 如果你的这个小数在计算机中能被精确表示,那么,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了,我该溜咯。


写在后面

今天一天啥也没做,把第三章拿下了,芜湖~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值