Python 被称为“batteries included”,就是因为有很多自带模块可以处理很多问题,极大简便了编程这一过程,当然,如果自己愿意造轮子,我只能给你666。
10.1 collections
namedtuple
namedtuple
是一个函数,它用来创建一个自定义的tuple对象,定义一个数据类型,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。https://docs.python.org/dev/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y']) #前者为类型,后者为对象?保持一致吧。。。
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2
>>> p
Point(x=1,y=2)
>>>isinstance(p,Point)
True
deque
相比于list
,deque
是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x') #list有的都有
>>> q.appendleft('y') # 特有头部添加删除元素
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
defaultdict
使用dict
时,如果引用的Key
不存在,就会抛出KeyError
。如果希望key
不存在时,返回一个默认值,防止报错,就可以用defaultdict
:
>>> from collections import defaultdict
>>> dd = defaultdict(lambda:'nonono')
>>> dd['key1'] = 'a'
>>> dd['key1']
'a'
>>> dd['key2']
'nonono'
>>> dd['key3']
'nonono'
OrderedDict
顾名思义,有序的dict
,可以迭代,需要注意的是它们的定义方法,冒号是和花括号结合的。
>>> from collections import OrderedDict
>>> d = dict([('a', 1), ('b', 2), ('c', 3)])
>>> d # dict的Key是无序的
{'a': 1, 'c': 3, 'b': 2}
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
#也可以 od=OrderedDict() od['a']=1...,而且Key会按照插入的顺序排列,不是Key本身排序
OrderedDict
可以实现一个FIFO(先进先出)的dict
,当容量超出限制时,先删除最早添加的Key
:
from collections import OrderedDict
class LastUpdatedOrderedDict(OrderedDict):
def __init__(self, capacity):
super(LastUpdatedOrderedDict, self).__init__()
self._capacity = capacity
def __setitem__(self, key, value):
containsKey = 1 if key in self else 0
if len(self) - containsKey >= self._capacity:
last = self.popitem(last=False)
print 'remove:', last
if containsKey:
del self[key]
print 'set:', (key, value)
else:
print 'add:', (key, value)
OrderedDict.__setitem__(self, key, value)
Counter
就是一个计数器:
>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
... c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})
10.2 base64
Base64是一种用64个字符来表示任意二进制数据的方法。
如果要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。Base64是一种最常见的二进制编码方法。
Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等。
Base64编码的长度永远是4的倍数。
- base64的编码都是按字符串长度,以每3个8bit的字符为一组,
- 然后针对每组,首先获取每个字符的ASCII编码,
- 然后将ASCII编码转换成8bit的二进制,得到一组3*8=24bit的字节
- 然后再将这24bit划分为4个6bit的字节,并在每个6bit的字节前面都填两个高位0,得到4个8bit的字节
- 然后将这4个8bit的字节转换成10进制,对照Base64编码表 ,得到对应编码后的字符。
如果要编码的二进制数据不是3的倍数,Base64用\x00
字节在末尾补足后,再在编码的末尾加上1个或2个=
号,表示补了多少字节,解码的时候,会自动去掉。
>>> import base64
>>> base64.b64encode('ghyglhw;.e')
'Z2h5Z2xodzsuZQ=='
>>> base64.b64decode('Z2h5Z2xodzsuZQ==')
'ghyglhw;.e'
由于标准的Base64编码后可能出现字符+
和/
,在URL中就不能直接作为参数,所以又有一种”url safe”的base64编码,其实就是把字符+
和/
分别变成-
和_
:
>>> base64.b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd++//'
>>> base64.urlsafe_b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd--__'
>>> base64.urlsafe_b64decode('abcd--__')
'i\xb7\x1d\xfb\xef\xff'
还可以自己定义64个字符的排列顺序,这样就可以自定义Base64编码,不过,通常情况下完全没有必要。
由于=字符也可能出现在Base64编码中,但=
用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=
去掉。
10.3 struct
Python提供了一个struct
模块来解决str
和其他二进制数据类型的转换。
struct
的pack
函数把任意数据类型变成字符串:
>>> import struct
>>> struct.pack('>I', 10240099)
'\x00\x9c@c'
pack的第一个参数是处理指令,'>I'
的意思是:
>
表示字节顺序是big-endian,也就是网络序,I
表示4字节无符号整数。
unpack
把str
变成相应的数据类型:
>>> struct.unpack('>IH', '\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)
根据>IH
的说明,后面的str
依次变为I
:4字节无符号整数和H
:2字节无符号整数。
10.3 hashlib
Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
>>> import hashlib
>>> md5 = hashlib.md5()
>>> md5.update('I love python')
>>> print md5.hexdigest()
856749c06f6f6630bd1733f0256a0fba
也可以分块儿多次调用update()
,最后再打印出来。
有没有可能两个不同的数据通过某个摘要算法得到了相同的摘要?完全有可能,因为任何摘要算法都是把无限多的数据集合映射到一个有限的集合中。这种情况称为碰撞。
设计一个验证用户登录的函数,根据用户输入的口令是否正确,返回True或False:
db = {
'michael': 'e10adc3949ba59abbe56e057f20f883e',
'bob': '878ef96e86145580c38c87f0410ad153',
'alice': '99b1c2188db85afee403b1536010c2c9'
}
def login(user, password):
if user in db:
md5_pwd = hashlib.md5()
md5_update(password)
if db[user] == md5_pwd.hexdigest():
print True
esle:
print False
else:
print False
由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
def calc_md5(password):
return get_md5(password + 'the-Salt')
10.4 itertools
>>> import itertools
>>> natuals = itertools.count(1)
>>> for n in natuals:
... print n
...
1
2
3
...
因为count()
会创建一个无限的迭代器,所以上述代码会打印出自然数序列,根本停不下来,只能按Ctrl+C
退出。
cycle()
会把传入的一个序列无限重复下去:
>>> import itertools
>>> cs = itertools.cycle([1,2,3])
>>> for c in cs:
... print c
...
1
2
3
....
1
2
repeat()
负责把一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数:
>>> ns = itertools.repeat('A', 10)
>>> for n in ns:
... print n
...
打印10次'A'
无限序列虽然可以无限迭代下去,但是通常我们会通过takewhile()
等函数根据条件判断来截取出一个有限的序列:
>>> natuals = itertools.count(1)
>>> ns = itertools.takewhile(lambda x: x <= 10, natuals)
>>> for n in ns: #这是对具体数值的操作
... print n
...
打印出1到10
chain()
chain()可以把一组迭代对象串联起来,形成一个更大的迭代器:
>>> for i in itertools.chain('ab','cd'):
... print i
...
a
b
c
d
groupby()
把迭代器中相邻的重复元素挑出来放在一起:
>>> for key, group in itertools.groupby('AAABBBCCAAA'):
... print key, list(group)
...
A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']
imap()
imap()
和map()
的区别在于,imap()
可以作用于无穷序列,并且,如果两个序列的长度不一致,以短的那个为准。
>>> for x in itertools.imap(lambda x, y: x * y, [10, 20, 30], itertools.count(1)):
... print x
...
10
40
90
注意imap()
返回一个迭代对象,而map()
返回list
。当你调用map()
时,已经计算完毕,必须用for循环对其进行迭代,才会在每次循环过程中计算出下一个元素。这说明imap()
实现了“惰性计算”,也就是在需要获得结果的时候才计算。如果把imap()
换成map()
去处理无限序列会有什么结果?算不出来= =,同理如ifilter()
。