# 2 collections 是Python内建的一个集合模块,提供了许多有用的集合类。
# 2.1 namedtuple
#tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:
p=(1,2)
#但是看到(1,2),很难看出这个tuple是用来表示一个坐标的。定义一个class又小题大做了,这时,namedtuple就派上用场了
from collections import namedtuple
Point=namedtuple('Point',['x','y'])
p=Point(1,2)
print(p.x,p.y)
#1 2
#namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。
#这样我们可以用namedtuple很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用。
#可以验证创建的Point对象是tuple的一种子类;
print(isinstance(p,Point),
isinstance(p,tuple))
#True True
#类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:
#namedtuple('名称',[属性list]):
Circle=namedtuple('Circle',['x','y','r'])
# 2.2 deque
#使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
#deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
from collections import deque
q=deque(['a','b','c'])
q.append('x')
q.appendleft('y')
print(q)
#deque(['y', 'a', 'b', 'c', 'x'])
#deque 除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。
# 2.3 defaultdict
#使用dict时,如果引用的key不存在,就会抛出keyerror.如果希望key不存在时,返回一个默认值,就可以用defaultdict:
from collections import defaultdict
dd=defaultdict(lambda:'N/A')
dd['key1']='abc'
#key1存在,key2不存在
print(dd['key1'],dd['key2'])
#abc N/A
#默认值是调用函数返回的,而函数在创建defaultdict对象时传入
#除了在key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的。
# 2.4 OrderedDict
#使用dict时,key是无序的。在对dict做迭代时,我们无法确定key的顺序。
#如果要保持key的顺序,可以用orderedDict:
from collections import OrderedDict
d=dict([('a',1),('b',2),('c',3)])
#dict的key是无序的
od=OrderedDict([('a',1),('b',2),('c',3)])
#OderedDict的key是有序的
print(d)
#{'c': 3, 'a': 1, 'b': 2}
print(od)
#OrderedDict([('a', 1), ('b', 2), ('c', 3)])
#orderedDict的key会按照插入的顺序排列,不是key本身排序:
od=OrderedDict()
od['z']=1
od['y']=2
od['x']=3
print(list(od.keys()))
#['z', 'y', 'x']
#OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的key:
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)
# 2.5 Counter是一个简单的计数器,例如,统计字符出现的个数:
from collections import Counter
c= Counter()
for ch in 'programming':
c[ch]=c[ch]+1
print(c)
#Counter({'g': 2, 'm': 2, 'r': 2, 'o': 1, 'n': 1, 'i': 1, 'p': 1, 'a': 1})
#Counter实际上也是dict的一个子类,上面的结果可以看出,字符‘g’、‘m’、‘r’各出现了两次,其他字符各出现了一次。
# 3 base64
#base64是一种用64个字符来表示任意二进制数据的方法。
#用记事本打开exe/jpg/pdf这些文件时,我们都会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符,所以,如果要让记事本这样的文本处理软件
#能处理二进制数据,就需要一个二进制到字符串的转换方法。Base64是一种最常见的二进制编码方法。
#base64的原理很简单,首先,准备一个包含64个字符的数组:
#['A','B','C',...'a','b','c',...'0','1',...'+','/']
#然后,对二进制数据进行处理,每3个字节一组,一共是3*8=24bit,划为4组,每组正好6个bit:
#这样我们得到4个数字作为索引,然后查表,获得相应的4个字符,就是编码后的字符串。
#所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后文本数据可以在邮件正文、网页等直接显示。
#如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,
#解码的时候,会自动去掉。
#Python内置的base64可以直接进行base64的编解码:
import base64
base64.b64encode(b'binary\x00string')
#b'YmluYXJ5AHN0cmluZw=='
base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
#b'binary\x00string'
#由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种‘url safe'的base64编码,其实就是把字符+和/分别变成-和_:
base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
#b'abcd++//'
base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
#b'abcd--__'
base64.urlsafe_b64decode('abcd--__')
#b'i\xb7\x1d\xfb\xef\xff'
#还可以自己定义64个字符的排列顺序,这样就可以自定义Base64编码,不过,通常情况下完全没有必要。
#Base64是一种通过查表的编码方法,不能用用于加密,即使使用自定义的编码表也不行。
#Base64适用于小段内容的编码,比如数字证书签名,Cookie的内容等
#由于=字符也可能出现在Base64编码中,但=用在URL。cookie里面会造成歧义,所以,很多base64编码会把=去掉:
#标准Base64:
#'abcd'->'YWJjZA=='
#自动去掉=:
#'abcd'->'YWJjZA'
#去掉=后怎么解码呢?因为Base64是把3个字节变成4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以
#正常解码了
#Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。
# 4 struct
#准确地讲,python没有专门处理字节的数据类型。但由于b'str'可以表示字节,所以,字节数组=二进制str.而在C语言中,我们可以很方便地用Struct/union来处理
#字节,以及字节和int,float的转换。
#在Python中,比方说要把一个32位无符号整数变成字节,也就是4个长度的bytes,你得配合位运算符这么写:
n=10240099
b1=(n & 0xff000000)>>24
b2=(n & 0xff0000)>>16
b3=(n & 0xff00)>>8
b4= n & 0xff
bs= bytes([b1,b2,b3,b4])
print(bs)
#b'\x00\x9c@c'
#非常麻烦。如果换成浮点数就无能为力了
#好在Python提供了一个struct模块来解决bytes和其他二进制数据类型的转换。
#struct的pack函数把任意数据类型变成bytes:
import struct
struct.pack('>I',10240099)
#b'\x00\x9c@c'
#pack的第一个参数是处理指令,'>I'的意思是:
#>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。
#后面的参数个数要和处理指令一致。
#unpack把bytes变成相应的数据类型:
struct.unpack('>IH',b'\xf0\xf0\xf0\xf0\x80\x80')
#(4042322160, 32896)
#根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数。
#所以,尽管Python不合适编写底层操作字节流的代码,但在对性能要求不高的地方,利用struct就方便多了。
#Windows的位图文件(.bmp)是一种非常简单的文件格式,我们来用struct分析一下。
#首先找一个bmp文件,没有的话用’画图‘画一个。
#读入前30个字节来分析:
s= b'\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'
#BMP格式采用小端方式存储数据,文件头的结构按顺序如下:
'''
两个字节:'BM'表示Windows位图,'BA'表示OS/2位图;
一个4字节整数:表示位图大小;
一个4字节整数:保留位,始终为0;
一个4字节整数:实际图像的偏移量;
一个4字节整数:Header的字节数;
一个4字节整数:图像宽度;
一个4字节整数:图像高度;
一个2字节整数:始终为1;
一个2字节整数:颜色数;
所以,组合起来用unpack读取:
'''
struct.unpack('<ccIIIIIIHH',s)
#(b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)
#结果显示,b'B',b'M'说明是Windows位图,位图大小为640*360,颜色数为24.
# 5 hashlib
#摘要算法简介
#hashib提供了常见的摘要算法,如MD5,SHA1等等。
#摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
#举个例子,你写了一篇文章,内容是一个字符串’how to use python hashlib - by Michael',并附上这篇文章的摘要是'2d73d4f15c0db7f5ecb321b6a65e5d6d'。
#如果有人篡改了你的文章,并发表为‘how to use python hashlib - by Bob’,你可以一下子指出Bob篡改了你的文章,因为根据‘how to use python hashlib - by Bob'
#计算出的摘要不同于原始文章的摘要。
#可见,摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
#摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,就算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit
#的修改,都会导致计算出的摘要完全不同。
#以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib
md5=hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
#'d26a53750bc40b38b65a520292f69306'
#如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
md5=hashlib.md5()
md5.update('how to use md5 in '.encode('utf-8'))
md5.update('python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
#d26a53750bc40b38b65a520292f69306
#试试改动一个字母,看看计算的结果是否完全不同。
#MD5是最常见的摘要算法,速度很快,生成结果是固定的128bit字节,通常用一个32位的16进制字符串表示。
#另一中常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
sha1=hashlib.sha1()
sha1.update('how to use shal in '.encode('utf-8'))
sha1.update('python hashlib?'.encode('utf-8'))
print(sha1.hexdigest())
#7b6ea50970f5991ad87193a81f9da954b180e1d4
#sha1的结果是160bit字节,通常用一个40位的16进制字符串表示。
#比sha1更安全的算法是sha256和sha512,不过越安全的算法不仅越慢,而且摘要长度更长。
#有没有可能两个不同的数据通过某个摘要算法得到了相同的摘要?完全有可能,因为任何摘要算法都是把无限多的数据集合映射到一个有限的集合中。这种情况称为
#碰撞,比如Bob试图根据你的摘要反推出一篇文章‘how to learn hashlib in python - by Bob’,并且这篇文章的摘要恰好和你的文章完全一致,这种情况也并非
#不可能出现,但是非常非常困难。
#摘要算法应用
#任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:
#如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。
#正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:
#当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令正确,如果不一致,口令肯定错误。
# 6 itertools
#python 的内建模块itertools提供了非常有用的用于操作迭代对象的函数
#无限迭代器 itertools
import itertools
'''
natuals=itertools.count(1)
for n in natuals:
print(n)
'''
#因为count()会创建一个无限的迭代器,所以上述代码会打印出自然数序列,根本停不下来,只能按Ctrl+c退出。
#cycle()会把传入的一个序列无限重复下去:
import itertools
cs=itertools.cycle('ABC')#注意字符串也是序列的一种
'''
for c in cs :
print(c)
'''
#同样停不下来。
#repeat()负责把一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数:
ns=itertools.repeat('A',3)
for n in ns:
print(n)
#A A A
#无限序列只有在for迭代时才会无限地迭代下去,如果只是创建了一个迭代对象,它不会事先把无限个元素生出来,实际上也不可能在内存中创建无限多个元素。
#无限序列虽然可以无限迭代下去,但是通常我们会通过takewhile()等函数根据条件判断来截取出一个有限的序列:
natuals=itertools.count(1)
ns=itertools.takewhile(lambda x:x <=10,natuals)
print(list(ns))
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
#itertools提供的几个迭代器操作函数更加有用:
#chain()
#chain()可以把一组迭代对象串联起来,形成一个更大的迭代器:
for c in itertools.chain('ABC','XYZ'):
print(c)
'''
A
B
C
X
Y
Z
'''
#groupby()
#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']
'''
#实际上挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。如果我们要忽略大小写
#分组,就可以让元素‘A’和‘a'都返回相同的key:
for key,group in itertools.groupby('AaaBBbcCAAa',lambda c:c.upper()):
print(key,list(group))
'''
A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']
A ['A', 'a', 'a']
B ['B', 'B', 'b']
C ['c', 'C']
A ['A', 'A', 'a']
'''
#itertools模块提供的全部是处理迭代功能的函数,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算。
# 7 contextlib
#在Python中,读写文件这样的资源要特别注意,必须在使用完毕后正确关闭它们。正确关闭文件资源的一个方法是使用try...finally:
'''
try:
f=open('/path/to/file','r')
f.read()
finally:
if f:
f.close()
'''
#写try...finally 非常繁琐。Python的with语句允许我们非常方便地使用资源,而不必担心资源没有关闭,所以上面的代码可以简化为:
'''
with open('/path/to/file','r') as f:
f.read()
'''
#并不是只有open()函数返回的fp对象才能使用with语句。实际上,任何对象,只要正确实现了上下文管理,就可以用于with语句。
#实现上下文管理是通过__enter__和__exit__这两个方法实现的。例如,下面的class实现了这两个方法:
class Query(object):
def __init__(self,name):
self.name=name
def __enter__(self):
print('Begin')
return self
def __exit__(self,exc_type,exc_value,traceback):
if exc_type:
print('Error')
else:
print('End')
def query(self):
print('Query info about %s...' % self.name)
#这样我们就可以把自己写的资源对象用于with语句:
with Query('Bob') as q:
q.query()
'''
Begin
Query info about Bob...
End
'''
#@contextmanager
#编写__enter__和__exit__仍然很繁琐,因此Python的标准库contextlib提供了更简单的写法,上面的代码可以改写如下:
from contextlib import contextmanager
class Query(object):
def __init__(self,name):
self.name=name
def query(self):
print('Query info about %s...' %self.name)
@contextmanager
def create_query(name):
print('Begin')
q=Query(name)
yield q
print('End')
#@contextmanager 这个decorator接受一个generator,用yield语句把with...as var 把变量输出出去,然后,with 语句就可以正常地工作了:
with create_query('Bob') as q:
q.query()
'''
Begin
Query info about Bob...
End
'''
#很多时候,我们希望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现。例如:
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
with tag("h1"):
print("hello")
print("world")
'''
<h1>
hello
world
</h1>
'''
#代码的执行顺序是:
'''
1.with语句首先执行yield之前的语句,因此打印出<h1>;
2.yield 调用会执行with语句内部的所有语句,因此打印出hello和world;
3.最后执行yield之后的语句,打印出</h1>
因此,@contextmanager让我们通过编写generator来简化上下文管理。
'''
#@closing
#如果一个对象没有实现上下文,我们就不能把它用于with语句。这个时候,可以用closing()来把该对象变为上下文对象。例如,
#用with语句使用uropen:
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
#closing也是一个经过@contextmanager装饰的generator,这个generator编写起来其实非常简单:
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
#它的作用就是把任意对象变为上下文对象,并支持with语句。
#@contextlib 还有一些其他的decorator ,便于我们编写更简洁的代码。
# 8 XML
#XML 虽然比JSON复杂,在web中应用也不如以前多了,不过仍有很多地方在用。
#DOM vs SAX
#操作XML有两种方法:DOM和SAX。DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。
#SAX是流模式,边读边解析,占用内存小,解决快,缺点是我们需要自己处理事件。
#正常情况下,有限考虑SAX,因为DOM实在太占内存。
#在python中使用SAX解析XML非常简洁,通常我们关心的事件是start_element,end_element和char_data,准备好这3个函数,然后就可以解析xml了。
#例如,当SAX解析器读到一个节点时:
#<a href="/">python</a>
#会产生3个事件:
'''
1.start_element事件,在读<a href="/">时;
2.char_data事件,在读取python时;
3.end_element事件,在读取</a>时。
'''
#XML解析实例
from xml.parsers.expat import ParserCreate
class DefaultSaxHandler(object):
def start_element(self,name,attrs):
print('sax:start_element:%s,attrs:%s' %(name,str(attrs)))
def end_element(self,name):
print('sax:end_element:%s'%name)
def char_data(self,text):
print('sax:char_data:%s' %text)
xml=r'''<?xml version="1.0"?>
<ol>
<li><a href="/python">Python</a></li>
<li><a href="/ruby">Ruby</a></li>
</ol>
'''
handler=DefaultSaxHandler()
parser=ParserCreate()
parser.StartElementHandler=handler.start_element
parser.EndElementHandler=handler.end_element
parser.CharacterDataHandler=handler.char_data
parser.Parse(xml)
#需要注意的是读取一大段字符串时,CharacterDataHandler可能被多次调用,所以需要自己保存起来,在EndElementHandler里面再合并。
#99%的情况下需要生成的XML结构都是非常简单的,因此,最简单也最有效的生成XML的方法是拼接字符串:
L=[]
L.append(r'<?xml version="1.0"?>')
L.append(r'<root>')
L.append(encode('some & data'))
L.append(r'</root>')
#return ''.join(L)
#如果要生成复杂的XML呢?那就改成JSON。
转载于:https://www.cnblogs.com/Ting-light/p/9548297.html