0%

Python - Theme 11 Package

Batteries Included Philosophy - having a rich and versatile standard library which is immediately available, without making the user download separate packages.

1. datetime

Python处理时间和日期的标准库

1.1. 获取当前时间和日期

1
2
3
4
5
6
7
8
>>> from datetime import datetime #datetime模块包含一个datetime类
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 7, 4, 10, 7, 8, 512524)
>>> print(now)
2020-07-04 10:07:08.512524
>>> print(type(now))
<class 'datetime.datetime'>

1.2. 获取指定日期和时间

1
2
3
>>> dt = datetime(2020, 6, 6, 6, 6) #指定日期
>>> print(dt)
2020-06-06 06:06:00

1.3. datetime转timestamp

在计算机中,时间的表示是相对于Epoch time来计算的。Epoch time: 也称Unix time,是1970年1月1号 00:00:00 UTC+00:00 的时间。当前时间就是相对于Epoch time的秒数。
全球各地计算机的任意时刻timestap都是相同的,这也就是为什么计算机的时间都是用timestamp表示了。

1
2
3
4
5
6
7
8
9
10
11
>>> dt = datetime(2020, 6, 6, 6, 6)
>>> print(dt)
2020-06-06 06:06:00
>>> dt.timestamp() #转换为timestamp
1591419960.0 #小数表示毫秒

>>> t = 1591419960.0
>>> print(datetime.fromtimestamp(t)) #本人在英国,夏时令为UTC+1
2020-06-06 06:06:00
>>> print(datetime.utcfromtimestamp(t)) #UTC时间
2020-06-06 05:06:00

1.4. str转datetime

1
2
3
>>> day = datetime.strptime('2020-06-06 06:06:00', '%Y-%m-%d %H:%M:%S') #日期的str与格式化字符串
>>> print(day)
>>> 2020-06-06 06:06:00

1.5. datetime转str

1
2
3
>>> now = datetime.now()
>>> print(now.strftime('%a, %b %d %H:%M'))
Sat, Jul 04 10:40

1.6. datetime加减

1
2
3
4
5
6
7
8
9
10
>>> from datetime import datetime, timedelta #通过timedelta类来进行加减运算
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 7, 4, 10, 44, 24, 184738)
>>> now + timedelta(hours = 12)
datetime.datetime(2020, 7, 4, 22, 44, 24, 184738)
>>> now - timedelta(days=1)
datetime.datetime(2020, 7, 3, 10, 44, 24, 184738)
>>> now + timedelta(days = 1, hours = 12)
datetime.datetime(2020, 7, 5, 22, 44, 24, 184738)

1.7. 本地时间转UTC

目前本地时间为英国夏时令(UTC+1)。datetime类型有一个时区属性tzinfo,默认为None。

1
2
3
4
5
6
7
8
9
10
>>> from datetime import datetime, timedelta, timezone
>>> tz_utc_1 = timezone(timedelta(hours = 1)) #创建时区UTC+1:00
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 7, 4, 10, 53, 38, 549365)
>>> dt = now.replace(tzinfo = tz_utc_1) #强制设置为UTC+1:00
>>> dt
datetime.datetime(2020, 7, 4, 10, 53, 38, 549365, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600)))
>>> print(dt)
2020-07-04 10:53:38.549365+01:00

1.8. 时区转换

1
2
3
4
5
6
>>> utc_dt = datetime.utcnow().replace(tzinfo = timezone.utc) #拿到UTC时间,并强制设置时区为UTC+0:00
>>> print(utc_dt)
2020-07-04 10:00:56.720584+00:00
>>> bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8))) #转换为北京时间
>>> print(bj_dt)
2020-07-04 18:00:56.720584+08:00

1.9. tips

datetime表示的时间需要时区信息才能确定一个时间,否则只能视为本地时间。
如果要存储datetime,最佳方法为timestamp,这样就与时区无关了。

2. collections

Python内建的集合模块

2.1. namedtuple - tuple的一个子类,既可以用下标访问也可以使用自定义的属性来访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#表示坐标
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y']) #namedtuple('名称', [属性list])
>>> p = Point(1,2)
>>> p.x
1
>>>p[0]
1
>>> p.y
2
>>> isinstance(p, Point)
True
>>> isinstance(p, tuple)
True

#表示学生
>>> Stu = namedtuple('Student', ['name', 'age'])
>>> s = Stu('Bob', '20')
>>> s
Student(name='Bob', age='20')
>>> s.name
'Bob'

2.2. deque

由于list为线性存储,数据量很大时,插入和删除的效率会很低。deque是为了实现插入和删除操作的双向列表,适用于队列和栈。

1
2
3
4
5
6
7
8
9
10
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
>>> q.popleft()
'y'
>>> q
deque(['a', 'b', 'c', 'x'])

2.3. defaultdict

使用dict时,如果引用的Key不存在,就会抛出KeyError。使用defaultdict使得程序不抛错误,返回一个默认值。

1
2
3
4
5
6
7
8
9
10
11
>>> from collections import defaultdict
>>> dd = defaultdict('Key not exit')
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: first argument must be callable or None
>>> dd = defaultdict(lambda: 'Key not exit') #必须是callable或者None,因此这里需要用函数
>>> dd['name'] = 'Bob'
>>> dd['name'] #key存在,和普通dict一样
'Bob'
>>> dd['age'] #key不存在,返回自己定义的返回值
'Key not exit'

2.4. OrderedDict

OrderedDict:保持key的顺序;
不过在python 3.6之后,普通dict也是有序的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import collections

print('\nRegular dictionary:')
d = {}
d['a'] = 'A'
d['b'] = 'B'
d['c'] = 'C'
for k, v in d.items():
print(k, v)

print('\nOrderedDict:')
d = collections.OrderedDict()
d['a'] = 'A'
d['b'] = 'B'
d['c'] = 'C'
for k, v in d.items():
print(k,v)

#输出:
Regular dictionary:
a A
b B
c C

OrderedDict:
a A
b B
c C

2.5. ChainMap

可以将多个字典映射组合在一起,组成一个逻辑上的dict,maps中存储字典组成的列表。查找的时候,会按照dict中的逻辑顺序查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> from collections import ChainMap
>>> a = {"name": "Bob"}
>>> b = {"age": 18}
>>> c = {"gender": "man"}
>>> pylook = ChainMap(a,b,c) #将多组字典合并
>>> pylook
ChainMap({'name': 'Bob'}, {'age': 18}, {'gender': 'man'})
>>> pylook['name'] #搜索key为name的value
'Bob'
>>> pylook.update({'name': 'Mack'}) #更新key为name的value(注意这里会从第一个字典开始找)
>>> pylook #第一个字典中存在key为name的元素,更新成功
ChainMap({'name': 'Mack'}, {'age': 18}, {'gender': 'man'})
>>> pylook.update({'age': '20'}) #发现第一个字典中无key为age的元素,则添加
>>> pylook
ChainMap({'name': 'Mack', 'age': '20'}, {'age': 18}, {'gender': 'man'})
>>> b['age'] = 22 #指定更新某个字典
>>> pylook
ChainMap({'name': 'Mack', 'age': '20'}, {'age': 22}, {'gender': 'man'})
>>> type(pylook.maps) #maps中存着用list保存的字典
<class 'list'>
>>> pylook.maps[0]['age'] = 25
>>> pylook.maps[1]['age'] = 35
>>> pylook.maps
[{'name': 'Mack', 'age': 25}, {'age': 35}, {'gender': 'man'}]
>>> pylook['age'] #尽管整个字典映射中有两个age,但是永远都从第一个开始找
25
>>> pylook.parents #类似于maps[1:]
ChainMap({'age': 35}, {'gender': 'man'})

应用:

1
2
3
#(1). 模拟Python内部查找链示例
import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#(2). 参数优先级查找(test5.py)
from collections import ChainMap
import os, argparse

#构造缺省参数
defaults = {
'color': 'red',
'user': 'guest'
}

#构造命令行参数
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
#命令行参数会存储在namespace中
print(vars(namespace).items())
#命令函参数字典
command_line_args = {k: v for k, v in vars(namespace).items() if v}

#利用这里来设置优先级: 命令函 > 系统环境 > 默认参数
combined = ChainMap(command_line_args, os.environ, defaults)

print('color=%s' % combined['color'])
print('user=%s' % combined['user'])

----------
#输入(无参数)
(venv3.7) ➜ python test5.py
#输出
dict_items([('user', None), ('color', None)])
color=red
user=guest

#输入(命令行参数)
(venv3.7) ➜ python test5.py -u Aaron
#输出
dict_items([('user', 'Aaron'), ('color', None)])
color=red
user=Aaron

#输入(环境变量+命令行参数)
(venv3.7) ➜ user=admin color=red python test5.py -u Aaron
#输出
dict_items([('user', 'Aaron'), ('color', None)])
color=red
user=Aaron

2.6. Counter -计数器

1
2
3
4
5
6
7
8
9
>>> from collections import Counter
>>> c = Counter('hello, word!')
>>> c
Counter({'l': 2, 'o': 2, 'h': 1, 'e': 1, ',': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1})
>>> c.update('aaaaaa') #可以继续添加字符,统计则会自动更新
>>> c
Counter({'a': 6, 'l': 2, 'o': 2, 'h': 1, 'e': 1, ',': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1})
>>> c.get('a') #查找指定的key
6

3. hashlib - 摘要算法

摘要算法:就像是一篇文章的摘要一样,永远不会变。摘要算法又称哈希算法、散列算法。它是通过一个函数把任意长度的数据转换为固定长度的数据串。只要原始数据没有发生改变,那么生成的数据串也不会变。Python的hashlib提供了常见的摘要算法,如:MD5,SHA1等等。摘要算法一般应用于防篡改。

3.1. MD5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> import hashlib
>>> md5 = hashlib.md5()
>>> md5.update('hello, world!'.encode('utf-8')) #计算哈希值
>>> md5.hexdigest()
'3adbbad1791fbae3ec908894c4963870'
>>> md5
<md5 HASH object @ 0x10dec70c0>

#如果数据过长也可多次调用update,结果一致
>>> md5 = hashlib.md5()
>>> md5
>>> <md5 HASH object @ 0x10dec7600>
>>> md5.update('hello, '.encode('utf-8'))
>>> md5.update('world!'.encode('utf-8'))
>>> md5.hexdigest()
'3adbbad1791fbae3ec908894c4963870'

#如果字符串有小小的改动,计算结果会完全不同
>>> md5 = hashlib.md5()
>>> md5.update('Hello, world!'.encode('utf-8'))
>>> md5.hexdigest()
'6cd3556deb0da54bca060b4c39479839'

3.2. SHA1

SHA1算法和MD5使用方法类似,不过生成的摘要更长,摘要越长越安全,但算法会越慢

1
2
3
4
5
>>> import hashlib
>>> sha1 = hashlib.sha1()
>>> sha1.update('hello, world!'.encode('utf-8'))
>>> asha1.hexdigest()
'1f09d30c707d53f3d16c530dd73d70a6ce7596a9'

3.3. Salt(加盐)

md5(password+’the-salt ‘): 这样黑客在不知道’the-salt’ 的情况下,是很难通过MD5反推明文的。不过为了避免两个用户使用了相同的password后生成相同的MD5值,在确定用户无法修改登录名的情况下,会将部分用户名作为’the-salt’的一部分,这样就能保证相同口令的用户存储成不同的MD5值。

4. hmac

Hmac算法:Keyed-Hashing for Message Authentication,通过一个标准算法,把key混入计算过程中,代替我们的salt算法,可以使得算法更加标准化。

1
2
3
4
5
6
7
8
>>> import hmac
>>> message = b'Hello, world!'
>>> key = b'secret' #在生产环境中,随机盐是明文保存的。
>>> h = hmac.new(key, message, digestmod='MD5')
>>> h
<hmac.HMAC object at 0x1087f71d0>
>>> h.hexdigest()
'fa4ee7d173f2d97ee79022d1a7355bcf'

5. itertools - 用于操作迭代对象的函数

5.1. count() - 无限迭代器

1
2
3
4
5
6
7
8
9
>>> import itertools
>>> nums = itertools.count(1) #从1开始的自然数
>>> for n in nums:
... print(n)
...
1
2
3
...Ctrl + c

5.2. cycle() - 将传入的序列无限循环

1
2
3
4
5
6
7
8
9
>>> sequence = itertools.cycle('abc')
>>> for c in sequence:
... print(c)
...
a
b
c
a
...Ctrl + c

5.3. repeat() -提供参数限制重复次数

1
2
3
4
5
6
7
>>> ns = itertools.repeat('ab', 3)
>>> for n in ns:
... print(n)
...
ab
ab
ab

5.4. takewhile() - 在无限迭代中设置结束条件

1
2
3
4
5
6
>>> nums = itertools.count(1)
>>> ns = itertools.takewhile(lambda x: x < 4, nums)
>>> ns
<itertools.takewhile object at 0x1087e1d70>
>>> list(ns)
[1, 2, 3]

5.5. chain() - 串联一组迭代对象,形成更大的迭代器

1
2
3
4
5
6
7
>>> for c in itertools.chain('AB', 'YZ'):
... print(c)
...
A
B
Y
Z

5.6. groupby() - 把迭代器中相邻的重复元素提取出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> for key, group in itertools.groupby('AAABBCAA'):
... print(key, list(group))
...
A ['A', 'A', 'A']
B ['B', 'B']
C ['C']
A ['A', 'A']

#忽略大小写
>>> for key, group in itertools.groupby('AaaBbCaA', lambda c: c.upper()): #挑选函数也是通过元素的返回值来判断是否相等的,因此可以通过函数使得'A'和'a'的返回值一致。
... print(key, list(group))
...
A ['A', 'a', 'a']
B ['B', 'b']
C ['C']
A ['a', 'A']