Python-collections — 容器数据类型

88 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

额,最近在写**模块的时候用到了这个collections模块,刚好全面的学习一下这个模块的用法,避免重复造轮子,写出更加Pythonic的代码

该篇文章发布于 Python-collection,一直有看这个博客的代码,并翻译了好几篇,今天刚好,完成collections模块文章的翻译。

配图是漫威宇宙中的 “收集者”

collections 模块包括内置类型列表,字典和元组的容器数据类型。

ChainMap — 搜索多个字典

ChainMap类管理一系列字典,并按它们的顺序搜索它们以查找与键相关联的值。 一个ChainMap是一个很好的“上下文”容器,因为它可以视为一个堆栈,堆栈随着堆栈的增长而发生变化,随着堆栈的缩小,这些更改将被丢弃。

Accessing Values(访问值)

ChainMap支持与常规词典相同的API来访问现有的值。

# collections_chainmap_read.py
import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)

print('Individual Values')
print('a = {}'.format(m['a']))
print('b = {}'.format(m['b']))
print('c = {}'.format(m['c']))
print()

print('Keys = {}'.format(list(m.keys())))
print('Values = {}'.format(list(m.values())))
print()

print('Items:')
for k, v in m.items():
    print('{} = {}'.format(k, v))
print()

print('"d" in m: {}'.format(('d' in m)))

子映射按照它们传递给构造函数的顺序进行搜索,因此为“c”键报告的值来自字典。

$ python3 collections_chainmap_read.py

Individual Values
a = A
b = B
c = C

Keys = ['c', 'b', 'a']
Values = ['C', 'B', 'A']

Items:
c = C
b = B
a = A

"d" in m: False

Reordering(重新排序)

ChainMap存储映射列表,它在映射属性列表中搜索映射。 这个列表是可变的,所以可以直接添加新的映射或者改变元素的顺序来控制查找和更新行为。

# collections_chainmap_reorder.py
import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)

print(m.maps)
print('c = {}\n'.format(m['c']))

# reverse the list
m.maps = list(reversed(m.maps))

print(m.maps)
print('c = {}'.format(m['c']))

当映射列表反转时,与'c'相关联的值发生变化。

$ python3 collections_chainmap_reorder.py

[{'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'}]
c = C

[{'b': 'B', 'c': 'D'}, {'a': 'A', 'c': 'C'}]
c = D

Updating Values(更新值)

ChainMap不会缓存子映射中的值。 因此,如果他们的内容被修改,结果会在访问ChainMap时反映出来。

# collections_chainmap_update_behind.py
import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)
print('Before: {}'.format(m['c']))
a['c'] = 'E'
print('After : {}'.format(m['c']))

更改与现有键关联的值并添加新元素的方式相同。

$ python3 collections_chainmap_update_behind.py

Before: C
After : E

虽然只有链中的第一个映射实际上被修改,但也可以直接通过ChainMap设置值。

# collections_chainmap_update_directly.py
import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)
print('Before:', m)
m['c'] = 'E'
print('After :', m)
print('a:', a)

当使用m存储新值时,将更新映射。

$ python3 collections_chainmap_update_directly.py

Before: ChainMap({'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'})
After : ChainMap({'a': 'A', 'c': 'E'}, {'b': 'B', 'c': 'D'})
a: {'a': 'A', 'c': 'E'}

ChainMap提供了一种方便的方法来创建一个新的实例,在映射列表的前面添加一个额外映射,以便轻松避免修改现有的基础数据结构。

# collections_chainmap_new_child.py
import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m1 = collections.ChainMap(a, b)
m2 = m1.new_child()

print('m1 before:', m1)
print('m2 before:', m2)

m2['c'] = 'E'

print('m1 after:', m1)
print('m2 after:', m2)

这种堆栈行为是将ChainMap实例用作模板或应用程序上下文的方便之处。 具体而言,在一次迭代中添加或更新值很容易,然后丢弃下一次迭代的更改。

$ python3 collections_chainmap_new_child.py

m1 before: ChainMap({'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'})
m2 before: ChainMap({}, {'a': 'A', 'c': 'C'}, {'b': 'B', 'c':
'D'})
m1 after: ChainMap({'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'})
m2 after: ChainMap({'c': 'E'}, {'a': 'A', 'c': 'C'}, {'b': 'B',
'c': 'D'})

对于新的上下文已知或预先构建的情况,也可以将映射传递给new_child()。

# collections_chainmap_new_child_explicit.py
import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
c = {'c': 'E'}

m1 = collections.ChainMap(a, b)
m2 = m1.new_child(c)

print('m1["c"] = {}'.format(m1['c']))
print('m2["c"] = {}'.format(m2['c']))

这样等同于

m2 = collections.ChainMap(c, *m1.maps)

产生

$ python3 collections_chainmap_new_child_explicit.py

m1["c"] = C
m2["c"] = E

Counter — Count Hashable Objects

Counter是一个容器,用于跟踪添加相等值的次数。 它可以用来实现其他语言通常使用bag或multiset数据结构的相同算法。

Initializing

# collections_counter_init.py
import collections

print(collections.Counter(['a', 'b', 'c', 'a', 'b', 'b']))
print(collections.Counter({'a': 2, 'b': 3, 'c': 1}))
print(collections.Counter(a=2, b=3, c=1))

所有三种形式的初始化结果都是相同的。

$ python3 collections_counter_init.py

Counter({'b': 3, 'a': 2, 'c': 1})
Counter({'b': 3, 'a': 2, 'c': 1})
Counter({'b': 3, 'a': 2, 'c': 1})

一个空的Counter 可以无参数构造,并通过update()方法填充。

# collections_counter_update.py
import collections

c = collections.Counter()
print('Initial :', c)

c.update('abcdaab')
print('Sequence:', c)

c.update({'a': 1, 'd': 5})
print('Dict    :', c)

计数值根据新数据增加,而不是替换。 在前面的例子中,a的计数从3到4。

$ python3 collections_counter_update.py

Initial : Counter()
Sequence: Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})
Dict    : Counter({'d': 6, 'a': 4, 'b': 2, 'c': 1})

Accessing Counts

一旦填充了Counter,就可以使用字典API检索其值。

# collections_counter_get_values.py
import collections

c = collections.Counter('abcdaab')

for letter in 'abcde':
    print('{} : {}'.format(letter, c[letter]))

Counter不会引发未知项的KeyError。 如果在输入中不存在某个值(如本例中的e),则其计数为0。

$ python3 collections_counter_get_values.py

a : 3
b : 2
c : 1
d : 1
e : 0

elements()方法返回一个迭代器,它产生Counter已知的所有项目。

# collections_counter_elements.py
import collections

c = collections.Counter('extremely')
c['z'] = 0
print(c)
print(list(c.elements()))

元素的顺序不能保证,并且计数小于或等于零的项目不包括在内(例子中z)。

$ python3 collections_counter_elements.py

Counter({'e': 3, 'x': 1, 't': 1, 'r': 1, 'm': 1, 'l': 1, 'y': 1,
'z': 0})
['e', 'e', 'e', 'x', 't', 'r', 'm', 'l', 'y']

使用most_common()生成n个最常遇到的输入值序列和它们各自的计数。

# collections_counter_most_common.py
import collections

c = collections.Counter()
with open('/usr/share/dict/words', 'rt') as f:
    for line in f:
        c.update(line.rstrip().lower())

print('Most common:')
for letter, count in c.most_common(3):
    print('{}: {:>7}'.format(letter, count))

这个例子计算系统字典中所有单词出现的字母,以产生一个频率分布,然后打印三个最常见的字母。 将most_common()的参数忽略,按频率顺序生成所有字母的列表。

$ python3 collections_counter_most_common.py

Most common:
e:  235331
i:  201032
a:  199554

Arithmetic

Counter实例支持算术和设置操作以汇总结果。 此示例显示了用于创建新Counter计数器实例的标准运算符,但也支持in-place运算符+ =, - =,&=和| =。

# collections_counter_arithmetic.py
import collections

c1 = collections.Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c2 = collections.Counter('alphabet')

print('C1:', c1)
print('C2:', c2)

print('\nCombined counts:')
print(c1 + c2)

print('\nSubtraction:')
print(c1 - c2)

print('\nIntersection (taking positive minimums):')
print(c1 & c2)

print('\nUnion (taking maximums):')
print(c1 | c2)

每次通过操作产生新的Counter时,任何具有零计数或负计数的项目都将被丢弃。 a的计数在c1和c2中相同,所以减法将其保留为零。

$ python3 collections_counter_arithmetic.py

C1: Counter({'b': 3, 'a': 2, 'c': 1})
C2: Counter({'a': 2, 'l': 1, 'p': 1, 'h': 1, 'b': 1, 'e': 1, 't': 1})

Combined counts:
Counter({'a': 4, 'b': 4, 'c': 1, 'l': 1, 'p': 1, 'h': 1, 'e': 1, 't': 1})

Subtraction:
Counter({'b': 2, 'c': 1})

Intersection (taking positive minimums):
Counter({'a': 2, 'b': 1})

Union (taking maximums):
Counter({'b': 3, 'a': 2, 'c': 1, 'l': 1, 'p': 1, 'h': 1, 'e': 1, 't': 1})

defaultdict — Missing Keys Return a Default Value(缺少key会返回一个默认值)

标准字典包含setdefault()方法,用于检索值并在值不存在时建立默认值。 相比之下,defaultdict允许调用者在容器初始化时指定默认的预先设置。

# collections_defaultdict.py
import collections


def default_factory():
    return 'default value'


d = collections.defaultdict(default_factory, foo='bar')
print('d:', d)
print('foo =>', d['foo'])
print('bar =>', d['bar'])

只要适用于所有键具有相同的默认值,此方法就可以正常工作。 它对默认值是用于聚合或累加值的类型(如列表,集合或甚至是int)特别有用。 标准库文档包括几个以这种方式使用defaultdict的示例。

$ python3 collections_defaultdict.py

d: defaultdict(<function default_factory at 0x101341950>,
{'foo': 'bar'})
foo => bar
bar => default value

deque — Double-Ended Queue

双端队列(或双端队列)支持从队列的任何一端添加和删除元素。 更常用的堆栈和队列是退化的低级形式,输入和输出仅限于单端。

# collections_deque.py
import collections

d = collections.deque('abcdefg')
print('Deque:', d)
print('Length:', len(d))
print('Left end:', d[0])
print('Right end:', d[-1])

d.remove('c')
print('remove(c):', d)

由于deques是一种序列容器,它们支持一些与列表相同的操作,例如使用__getitem __()检查内容,确定长度,并通过匹配标识从队列中移除元素。

$ python3 collections_deque.py

Deque: deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
Length: 7
Left end: a
Right end: g
remove(c): deque(['a', 'b', 'd', 'e', 'f', 'g'])

Populating

可以在Python实现中从任一端填充deque,称为“左”和“右”。

# collections_deque_populating.py
import collections

# Add to the right
d1 = collections.deque()
d1.extend('abcdefg')
print('extend    :', d1)
d1.append('h')
print('append    :', d1)

# Add to the left
d2 = collections.deque()
d2.extendleft(range(6))
print('extendleft:', d2)
d2.appendleft(6)
print('appendleft:', d2)

extendleft()函数迭代其输入并为每个项目执行appendleft()的等效操作。 最终的结果是deque包含反向顺序的输入序列。

$ python3 collections_deque_populating.py

extend    : deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
append    : deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
extendleft: deque([5, 4, 3, 2, 1, 0])
appendleft: deque([6, 5, 4, 3, 2, 1, 0])

Consuming

类似地,取决于所应用的算法,可以从两端或任一端消耗双端队列的元素。

# collections_deque_consuming.py
import collections

print('From the right:')
d = collections.deque('abcdefg')
while True:
    try:
        print(d.pop(), end='')
    except IndexError:
        break
print()

print('\nFrom the left:')
d = collections.deque(range(6))
while True:
    try:
        print(d.popleft(), end='')
    except IndexError:
        break
print()

使用pop()从双端队列的“右”端移除一个项目,然后从popleft()中取出一个项目,从“左”端开始。

$ python3 collections_deque_consuming.py

From the right:
gfedcba

From the left:
012345

由于deques是线程安全的,所以甚至可以从不同的线程同时消耗两端的内容。

# collections_deque_both_ends.py
import collections
import threading
import time

candle = collections.deque(range(5))


def burn(direction, nextSource):
    while True:
        try:
            next = nextSource()
        except IndexError:
            break
        else:
            print('{:>8}: {}'.format(direction, next))
            time.sleep(0.1)
    print('{:>8} done'.format(direction))
    return


left = threading.Thread(target=burn,
                        args=('Left', candle.popleft))
right = threading.Thread(target=burn,
                         args=('Right', candle.pop))

left.start()
right.start()

left.join()
right.join()

此示例中的线程在每个末端之间交替,直到deque为空为止删除项目。

 $ python3 collections_deque_both_ends.py

 Left: 0
Right: 4
Right: 3
 Left: 1
Right: 2
 Left done
Right done

Rotating

双端队列的另一个有用方面是能够在任一方向上旋转它,以便跳过一些项目。

# collections_deque_rotate.py
import collections

d = collections.deque(range(10))
print('Normal        :', d)

d = collections.deque(range(10))
d.rotate(2)
print('Right rotation:', d)

d = collections.deque(range(10))
d.rotate(-2)
print('Left rotation :', d)

向右旋转双端(使用正向旋转)从右端取出元素并将它们移动到左端。 向左旋转(带负值)从左端获取元素并将它们移动到右端。 这可能有助于将外观上的元素可视化为沿着刻度盘的移动。

$ python3 collections_deque_rotate.py

Normal        : deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Right rotation: deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
Left rotation : deque([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])

Constraining the Queue Size

一个deque实例可以配置一个最大长度,以便它永远不会超出该大小。 当队列达到指定长度时,添加新项目时会丢弃现有项目。 此行为对于查找未确定长度的流中的最后n个项目很有用。

# collections_deque_maxlen.py
import collections
import random

# Set the random seed so we see the same output each time
# the script is run.
random.seed(1)

d1 = collections.deque(maxlen=3)
d2 = collections.deque(maxlen=3)

for i in range(5):
    n = random.randint(0, 100)
    print('n =', n)
    d1.append(n)
    d2.appendleft(n)
    print('D1:', d1)
    print('D2:', d2)

无论物品添加到哪个末端,都会保持deque长度。

$ python3 collections_deque_maxlen.py

n = 17
D1: deque([17], maxlen=3)
D2: deque([17], maxlen=3)
n = 72
D1: deque([17, 72], maxlen=3)
D2: deque([72, 17], maxlen=3)
n = 97
D1: deque([17, 72, 97], maxlen=3)
D2: deque([97, 72, 17], maxlen=3)
n = 8
D1: deque([72, 97, 8], maxlen=3)
D2: deque([8, 97, 72], maxlen=3)
n = 32
D1: deque([97, 8, 32], maxlen=3)
D2: deque([32, 8, 97], maxlen=3)

namedtuple — Tuple Subclass with Named Fields(带有命名字段的元组子类)

标准元组使用数字索引来访问其元素。

# collections_tuple.py
bob = ('Bob', 30, 'male')
print('Representation:', bob)

jane = ('Jane', 29, 'female')
print('\nField by index:', jane[0])

print('\nFields by index:')
for p in [bob, jane]:
    print('{} is a {} year old {}'.format(*p))

这使元组容易使用。

$ python3 collections_tuple.py

Representation: ('Bob', 30, 'male')

Field by index: Jane

Fields by index:
Bob is a 30 year old male
Jane is a 29 year old female

相比之下,记住每个值应使用哪个索引可能会导致错误,特别是如果元组有很多字段并且构造得远离它的使用位置。 namedtuple为每个成员分配名称以及数字索引。