python标准库学习(1)
标准库collections
2019-08-07
标准库collections
为python在列表(list), 元组(tuple), 字典(dict)等基础上,增加了几种数据类型,这些类型可以视作是对原有的几种数据类型的扩展:
数据类型 | 作用 |
---|---|
namedtuple | 可以用属性名称来访问的元组 |
deque | 双向(前后)队列 |
Counter | 可以用属性名称来访问的元组 |
OrderedDict | 有序字典 |
defaultdict | 有默认值的字典 |
ChainMap | 将多个字典合并在一个映射中 |
除以上这些数据类型以外,还有UserDict
、UserList
、UserString
三个抽象基类,涉及这部分的暂不讨论。
1 collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
namedtuple,可以称为具名元组或命名元组,是对元组(tuple)的继承和扩展,被称为是一个“工厂函数”(factory function)。一般而言,元组中的元素(item)只能通过索引(index)进行访问,例如:
a_tuple = ('a', 'b', 'c', 2, 3, 4)
s = a_tuple[-1] # 结果是 4
namedtuple扩展了另一种方式,使得元组中的元素可以通过名称(name)的方式访问到,这样做的好处是在利用元组储存数据时,每个元素的含义就很清楚,例如可以创建一个名为Staff的namedtuple:
from collections import namedtuple
Staff=namedtuple("Staff",['name','age','email'])
mars = Staff('Mars', 30, 'mars03@*****.com')
june = Staff('June', 27, 'june_hcs@*****.com')
june.email #结果为'june_hcs@*****.com'
可以发现这与定义一个名为Staff的类,构造函数(constructor,__init__
)中定义name等3个属性类似。 这也是其被称为工厂函数的一个原因。
namedtuple中封装了3个比较有用的函数:_make
,_replace
和_asdict
。_make
可以将普通的元组转为一个namedtuple:
# 将一个普通元组转为Staff
kar = ('Kar',31,'kcar@****.com')
kar_nmdtp = Staff._make(kar) # Staff(name='Kar', age=31, email='kcar@****.com')
_replace
允许使用属性名(name)来替换元组中的值:
kar = Staff('Kar',31,'kcar@****.com')
kar_new = kar._replace(email='kar03@****.com') # 注意,kar这个元组中email的值不会被修改
_asdict
会将一个namedtuple转为一个OrderedDict对象:
kar = Staff('Kar',31,'kcar@****.com')
kar._asdict() # OrderedDict([('name', 'Kar'), ('age', 31), ('email', 'kcar@****.com')])
2 collections.deque([iterable[, maxlen]])
deque是双边队列(double-ended queue)的缩写,参数maxlen
指定了这个列表最大的长度。
既然称为“双边队列”,那么对应地,列表的插入、抛出等操作就可以扩展至两边,即增加一组带有left
的操作:
from collections import deque
dlst = deque(['a','b','c','d','e','f','g'])
dlst.append('h') # deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
dlst.appendleft('0') # deque(['0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
dlst.pop() # deque(['0', 'a', 'b', 'c', 'd', 'e', 'f', 'g'])
dlst.popleft() # deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
dlst.extend(['h','i']) # deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
dlst.extendleft(['0','1']) # deque(['1', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
dlst.rotate(2) # deque(['h', 'i', '1', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g'])
最后一个rotate(n)
方法的作用是,当n
>
>
> 0时,将末尾n个元素移到开头,反之当n
<
<
< 0 时则将头部的n个元素移至末尾。
参数maxlen
可以用来指定列表最大长度,当达到这个最大长度时,超越长度的元素将被从列表中移除:
from collections import deque
dlst = deque(['a','b','c','d','e'], maxlen=8)
dlst.extend(i for i in 'hijklmn') # deque(['e', 'h', 'i', 'j', 'k', 'l', 'm', 'n'])
3 collections.Counter([iterable-or-mapping])
Counter
是一个带有计数功能的容器类,是字典(dict)的一个子类,对于可哈希对象,Counter
统计元素的次数。下面这个例子会将字符串中每个字符出现的次数进行统计,返回一个字典
from collections import Counter
s_str = '''It was the best of times, \
it was the worst of times, \
it was the age of wisdom, \
it was the age of foolishness, \
it was the epoch of belief,\
it was the epoch of incredulity, \
it was the season of Light,it was the season of Darkness, \
it was the spring of hope, it was the winter of despair,\
we had everything before us,we had nothing before us, \
we were all going direct to Heaven, \
we were all going direct the other way--in short,\
the period was so far like the present period, \
that some of its noisiest authorities insisted on its being received,\
for good or for evil, in the superlative degree of comparison only.'''
# 这里先去掉字符串中的标点、空格,再统计字母出现的频数
s_str = s_str.replace(' ','').replace(',','').replace('.','').replace('-','')
r = Counter(s_str)
# Counter({'e': 69, 't': 48, 'o': 44, 'i': 44, 's': 42, 'a': 28,
# 'h': 27, 'r': 27, 'n': 22, 'w': 21, 'f': 19, 'g': 13, 'd': 13,
# 'l': 11, 'p': 10, 'c': 7, 'b': 5, 'm': 5, 'u': 5, 'v': 5,
# 'y': 4, 'k': 2, 'I': 1, 'L': 1, 'D': 1, 'H': 1})
Counter
提供了一些很有用的函数,其中一个是most_common(n=None)
,用于统计出现频次最高的1个(默认)或几个(指定n)元素:
# 接上面的例子
r.most_common(2) # [('e', 69), ('t', 48)],注意这是个列表,其中的元素是元组
elements()
返回一个可迭代对象,将元素按照出现次数进行迭代:
v_str = 'Each thing, as far as it can by its own power, strives to persevere in its being'
v_cnt = Counter(v_str.replace(' ','').replace(',',''))
sorted(v_cnt.elements()) # 返回一个列表,将v_str中元素按顺序迭代出现的次数
参考源代码可以发现,在定义Counter
类时,还封装了__add__
、__sub__
、__iadd__
、__isub__
、__and__
、__or__
等几个特殊方法,使得Counter
对象之间可以实现类似加减、并、交的计算(这里直接引用源代码中的示例):
# 对结果进行加减
Counter('abbb') + Counter('bcc') # Counter({'b': 4, 'c': 2, 'a': 1})
Counter('abbbc') - Counter('bccd') # Counter({'a': 1, 'b': 2})
c = Counter('abbbc')
c -= Counter('bccd') # Counter({'b': 2, 'a': 1})
# 并、交运算
Counter('abbb') | Counter('bcc') # Counter({'b': 3, 'c': 2, 'a': 1})
Counter('abbb') & Counter('bcc') # Counter({'b': 1})
4 collections.OrderedDict(dict)
OrderedDict在定义字典的同时,维护了一个记录元素插入顺序的链表,使得字典成为有顺序的,正常迭代会遵循LIFO(先进后出)的顺序:
from collections import OrderedDict
a_odct = OrderedDict()
a_odct['a'] = 'a101'
a_odct['b'] = 'a102'
a_odct['c'] = 'a103'
for k, v in a_odct.items():
print(k, ':', v)
# a : a101
# b : a102
# c : a103
因为OrderedDict中字典有了排序,因此可以像列表一样进行堆栈操作,利用popitem(last=True)
可以实现:
a_odct = OrderedDict()
a_odct['a'] = 'a101'
a_odct['b'] = 'a102'
a_odct['c'] = 'a103'
a_odct['d'] = 'b101'
a_odct.popitem(last=False) # ('a', 'a101')
popitem()
在last
参数默认为True,可以实现LIFO(先进后出,Last In First Out),设为False时,可以实现FIFO(先进先出, First Input First Output)。
另一个方法move_to_end(key, last=True)
,可以通过制定键将一个键值对移到头部或尾部,last
默认参数为True,即将制定的元素移到尾部:
a_odct = OrderedDict()
a_odct['a'] = 'a101'
a_odct['b'] = 'a102'
a_odct['c'] = 'a103'
a_odct['d'] = 'b101'
a_odct.move_to_end('b') # 原字典键变为 OrderedDict([('a', 'a101'), ('c', 'a103'), ('d', 'b101'), ('b', 'a102')])
5 collections.defaultdict([default_factory[, …]])
与namedtuple类似,defaultdict也是一个工厂函数,对于普通字典而言,当键不存在时,会发生KeyError
:
from collections import defaultdict
a_dct = {'a':'a101', 'b':'a102','c':'a103', 'd':'b101'}
a_dct['r'] # KeyError: 'r'
对于不存在的键,可以通过get()
方法设置默认值。defaultdict
提供了另一种方式来解决KeyError的问题,即为字典设置默认值,当键不存在时返回一个默认的结果。
defaultdict
是dict的一个子类,在初始化时需要提供一个类型或者不带参数的可调用函数作为参数,如果是一个函数,那么defaultdict
字典的默认值将会是函数的返回值:
from collections import defaultdict
a_dft = defaultdict(int)
a_dft['a'] # 0,类似地,如果传入的参数是list,将会返回[]
from random import randint
from collections import defaultdict
a_dfd = defaultdict(lambda: randint(0, 100)) # 注意这里作为传入参数的匿名函数,没有参数
a_dfd['r'] = 23
a_dfd['s'] = 41
a_dfd['a'] # 会返回一个0和100之间的随机整数
以下这个统计次数的例子有助于更好理解defaultdict
的作用,这也是其经常被与Counter
做对比的原因。
cnt_lst = ['c','b','a','c','d','a','d','c','d','a']
# 这种方式会返回KeyError,因为字典中没有c这个键
cnt = {}
for cr in cnt_lst:
cnt[cr] +=1
# 这种方式稍改进了上面的逻辑,增加了对键是否存在的判断
cnt = {}
for cr in cnt_lst:
if cr not in cnt:
cnt[cr] = 1
else:
cnt[cr] += 1
cnt
# 利用字典中setdefault方法也能实现,dict.setdefault(key, default=None)方法,如果key存在则返回值,如果不存在则设为默认值
cnt = {}
for cr in cnt_lst:
cnt[cr] = cnt.setdefault(cr, 0) + 1
# 如果一开始就将cnt设为一个defaultdict,则可以很简单实现
cnt = defaultdict(int)
for cr in cnt_lst:
cnt[cr] += 1
6 collections.ChainMap(*maps)
ChainMap
可以称为“组合字典”,根据官方文档的介绍,“一个 ChainMap 类是为了将多个映射快速的链接到一起,这样它们就可以作为一个单元处理。它通常比创建一个新字典和多次调用 update() 要快很多。”
ChainMap
可以接收任意多个字典并将这些字典“合”在一起,之所以用加引号的合(并)来说ChainMap
的作用,是因为本质上它并没有把几个字典做合并,而是建立了一个映射关系,这样既不会改变原有字典的内容,又能比较迅速的实现合并操作:
from collections import ChainMap
a_dct = {'a':1, 'b':2}
b_dct = {'c':3, 'd':4}
c_dct = {'a':5, 'c':6}
r = ChainMap(a_dct, b_dct, c_dct) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
print(r['b']) # 2
print(r['a']) # 1
最后一个r['a']
的例子可以发现,在键(key)存在相同的情况时,ChainMap
会从根据自己的顺序找到一个值。
下面来看改变所映射的字典会对构建的ChainMap
产生什么影响:
from collections import ChainMap
a_dct = {'a':1, 'b':2}
b_dct = {'c':3, 'd':4}
c_dct = {'a':5, 'c':6}
r = ChainMap(a_dct, b_dct, c_dct) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
r['s'] = 11 # ChainMap({'a': 1, 'b': 2, 's': 11}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6}),此时a_dct将被改变成为{'a': 1, 'b': 2, 's': 11}
b_dct.pop('c') # ChainMap({'a': 1, 'b': 2, 's': 11}, {'d': 4}, {'a': 5, 'c': 6})
c_dct.update({'c':9}) # ChainMap({'a': 1, 'b': 2, 's': 11}, {'d': 4}, {'a': 5, 'c': 9})
可以发现,如果改变原字典,则ChainMap
会相应发生变化;如果对ChainMap
添加键值对,则这一组数据将被添加在ChainMap
的第一个字典中,并会改变原有的字典数据。
ChainMap
有3个方法:map
、new_child(m=None)
、parents
。map
返回“一个可以更新的映射列表。这个列表是按照第一次搜索到最后一次搜索的顺序组织的。它是仅有的存储状态,可以被修改。”
from collections import ChainMap
a_dct = {'a':1, 'b':2}
b_dct = {'c':3, 'd':4}
c_dct = {'a':5, 'c':6}
r = ChainMap(a_dct, b_dct, c_dct) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
print(r.maps) # [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6}]
r.maps.append({'w':3})
print(r) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6}, {'w': 3})
new_child(m=None)
则实现了“返回一个新的ChainMap
类,包含了一个新映射(map),后面跟随当前实例的全部映射(map)。如果 m 被指定,它就成为不同新的实例,就是在所有映射前加上 m,如果没有指定,就加上一个空字典,这样的话一个 d.new_child()
调用等价于 ChainMap({}, *d.maps)
。这个方法用于创建子上下文,不改变任何父映射的值。”
from collections import ChainMap
a_dct = {'a':1, 'b':2}
b_dct = {'c':3, 'd':4}
c_dct = {'a':5, 'c':6}
r = ChainMap(a_dct, b_dct, c_dct) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
s = r.new_child() # s将是ChainMap({}, {'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6}),r则不会发生改变
s['w'] = 12
print(s) # ChainMap({'w': 12}, {'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
如果参数m被指定,则它会被添加到ChainMap
的最前面。
from collections import ChainMap
a_dct = {'a':1, 'b':2}
b_dct = {'c':3, 'd':4}
c_dct = {'a':5, 'c':6}
r = ChainMap(a_dct, b_dct, c_dct) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
s = r.new_child(c_dct) # ChainMap({'a': 5, 'c': 6}, {'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6}),r则不会发生改变
parents
“属性返回一个新的 ChainMap
包含所有的当前实例的映射,除了第一个。这样可以在搜索的时候跳过第一个映射。 ”
from collections import ChainMap
a_dct = {'a':1, 'b':2}
b_dct = {'c':3, 'd':4}
c_dct = {'a':5, 'c':6}
r = ChainMap(a_dct, b_dct, c_dct) # ChainMap({'a': 1, 'b': 2}, {'c': 3, 'd': 4}, {'a': 5, 'c': 6})
s = r.parents # s将成为ChainMap({'c': 3, 'd': 4}, {'a': 5, 'c': 6}),r则不会发生改变
v = r.parents.parents # v将变成ChainMap({'a': 5, 'c': 6}), r和s不会发生变化