python标准库学习(1):collections

python标准库学习(1)

标准库collections

2019-08-07

标准库collections为python在列表(list), 元组(tuple), 字典(dict)等基础上,增加了几种数据类型,这些类型可以视作是对原有的几种数据类型的扩展:

数据类型作用
namedtuple可以用属性名称来访问的元组
deque双向(前后)队列
Counter可以用属性名称来访问的元组
OrderedDict有序字典
defaultdict有默认值的字典
ChainMap将多个字典合并在一个映射中

除以上这些数据类型以外,还有UserDictUserListUserString三个抽象基类,涉及这部分的暂不讨论。

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 &gt; &gt; > 0时,将末尾n个元素移到开头,反之当n &lt; &lt; < 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个方法:mapnew_child(m=None)parentsmap返回“一个可以更新的映射列表。这个列表是按照第一次搜索到最后一次搜索的顺序组织的。它是仅有的存储状态,可以被修改。”

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不会发生变化
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值