Whoosy's Blog

藏巧于拙 用晦而明 寓清于浊 以屈为伸

0%

Python内置库Collections介绍

编码不易,转载请注意出处!

介绍

Source code: Lib/collections/init.py

Collections(容器数据类型)模块是Python的内置库,在python2.4版本被首次引入。
python的内置数据容器dict、list、set、tuple有的场景并不适用,Collections则提供更为复杂的数据容器的一些操作,可以认为它是Python内置数据容器的替代选择。
在本文章中,我将向您介绍Python Collections模块中的6种最常用的数据结构。它们如下:

容器 中文名称 简介
namedtuple 命名元组 创建命名元组子类的工厂函数
deque 双端队列 类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop)
Counter 计数器 字典的子类,提供了可哈希对象的计数功能
OrderedDict 有序字典 字典的子类,保存了他们被添加的顺序
defaultdict 带有默认值的字典 字典的子类,提供了一个工厂函数,为字典查询提供一个默认值
ChainMap 链式字典(链图) 类似字典(dict)的容器类,将多个映射集合到一个视图里面

只看简介可能一脸懵逼,下面我将详细为您介绍以上数据容器用法。

namedtuple

namedtuple()返回一个具有元组中每个位置名称的元组。使用普通元组的最大问题之一是必须记住元组对象的每个字段的索引。这显然是困难的。命名元组被引入来解决这个问题。

创建一个namedtuple

1
2
3
4
5
6
7
8
from collections import namedtuple

Fish = namedtuple("Fish", ["name", "species", "tank"])
sammy = Fish("Sammy", "shark", "tank-a")
print(sammy)

# Output
Fish(name='Sammy', species='shark', tank='tank-a')

namedtuple()函数有两个参数:命名元组名称Fish和元素列表["name", "species", "tank"]

实例化Fish对象为sammysammy的字段数据可以通过其名称或传统的元组索引进行访问:

1
2
3
4
5
6
print(sammy.species)
print(sammy[1])

# Output
shark
shark

namedtuple在collections模块中使用可以使您的程序更具可读性,同时保持元组的重要属性(它们是不可变的且有序的)。
此外,namedtuple函数还有一些额外的私有方法。

  1. _asdict()把实例转换为字典
    1
    2
    3
    4
    5
    6
    7
    print(sammy._asdict())

    #Output

    {'name': 'Sammy', 'species': 'shark', 'tank': 'tank-a'} # Python3.8之后

    OrderedDict([('name', 'Sammy'), ('species', 'shark'), ('tank', 'tank-a')]) # python3.8之前

低于3.8的Python版本会得到一个有序字典OrderedDict,高于3.8的则会得到一个普通字典。

  1. 使用_replace()函数更改字段值
1
2
3
4
5
6
7
sammy1 = sammy._replace(name='Whoosy')
print(sammy1)
print(sammy)

# Output
Fish(name='Whoosy', species='shark', tank='tank-a')
Fish(name='Sammy', species='shark', tank='tank-a')

注意,_replace()函数会返回一个新的实例,它并不会改变现有实例的值。

deque双端队列

Python中的列表是可变的有序元素序列,Python可以很轻松的使用append()方法对一个列表实现追加元素,并且列表的长度对于追加时间没有任何影响。但是如果使用insert()在列表开头插入会因为列表长度的增大导致插入时间增大。
追加操作时间复杂度是O(1),相比之下,插入列表的开头则是O(n)。

我们可以在Python列表的开头插入:

1
2
3
4
5
6
test_list = ['a', 'b', 'c']
test_list.insert(0, 'x')
print(test_list)

# Output
['x', 'a', 'b', 'c']

.insert(index, object)方法使我们能够将"x"元素插入到列表开头。插入列表开头的时间复杂度是O(n)。随着列表长度的增加,在列表开头插入元素的时间将成比例地增加,花费的时间将越来越长。假如我们的列表元素非常多,此时,list数据就并不满足我们的需求了。
collections模块中的deque(双端队列)是一个类似列表的数据结构,它使我们能够以恒定的时间复杂度O(1)在序列的开头或结尾插入新元素。

创建一个deque对象:

1
2
3
from collections import deque

favorite_fish_deque = deque(["Sammy", "Jamie", "Mary"])

在开头插入一条数据

1
favorite_fish_deque.appendleft("Alice")

无论此队列中有多少元素,此时我们向队列开头插入一条数据时间复杂度一直是O(1)。
需要注意的是,尽管向deque序列的开头添加元素的效率比列表更高,但deque执行所有操作的效率并不都比列表高。
例如,访问deque中的随机项时间复杂度是O(n),但是访问列表中的随机项时间复杂度是O(1)。所以,当需要快速插入或移除集合两边的元素时,使用deque。

其他方法
追加元素:

1
favorite_fish_deque.append('Whoosy')

从最右端删除元素:

1
favorite_fish_deque.pop()

从最左端删除元素:

1
favorite_fish_deque.popleft()

清空队列:

1
favorite_fish_deque.clear()

元素在队列中出现的次数:

1
favorite_fish_deque.count('Whoosy')

Counter

Counter是dictionary对象的子类。
collections模块中的Counter()函数接受一个可迭代对象作为参数,并返回一个字典。
在这个字典中,键是可迭代对象中的元素,值是元素在可迭代对象中出现的次数。

创建一个Counter对象

1
2
3
from collections import Counter

cnt = Counter(["a", "b", "c", "d", "a", "b"])

使用

1
2
3
4
5
6
7
8
9
10
print(cnt)
print(cnt["a"])
print(cnt["b"])
print(cnt["f"])

# Output
Counter({'a': 2, 'b': 2, 'c': 1, 'd': 1})
2
1
0

Counter还有一些方法函数:

  • elements()
  • most_common([n])
  • subtract([interable-or-mapping])

elements()

elements()函数可以帮你展开Counter对象的所有值。
例如:

1
2
3
4
5
6
7
from collections import Counter

cnt = Counter({"a": 2, "b": 2, "c": 1})
print(list(cnt.elements()))

# Output
['a', 'a', 'b', 'b', 'c']

most_common()

Counter对象返回的返回一个无序的字典。我们可以使用Counter对象的most_common()函数来根据每个元素中的计数的数量对其进行排序。

1
2
3
4
5
6
7
8
9
from collections import Counter

list = [1,2,3,4,1,2,6,7,3,8,1]
cnt = Counter(list)
print(cnt.most_common())


# Output
[(1, 3), (2, 2), (3, 2), (4, 1), (6, 1), (7, 1), (8, 1)]

可以看到,most_common函数返回一个列表,该列表是根据元素的数量排序的。1的计数为3,因此它是列表的第一个元素。

subtract()

subtract()接受iterable(列表)或映射(字典)作为参数,并使用该参数和之前的可迭代对象相减从而推断元素最新的计数。
例如:

1
2
3
4
5
6
7
8
9
from collections import Counter

cnt = Counter({1:3,2:4})
deduct = {1:1, 2:2}
cnt.subtract(deduct)
print(cnt)

# Output
Counter({1: 2, 2: 2})

你可以注意到我们首先创建的cnt对象,对于“1”的计数为3,对于“2”的计数为4。新的字典中键“1”值为“1”,键“2”值为“2”。subtract()函数从键’1’中扣除1个计数,从键’2’中扣除2个计数。从而得到最新的Counter对象。

OrderedDict有序字典

Python中的字典是无序的,但是我们想要一种有序的字典,这时候就需要用到OrderedDict数据容器了。

创建一个OrderedDict对象
在下面的代码中,您创建了一个不带任何参数的OrderedDict对象。在此之后,您可以向用python字典的操作一样来使用此对象。

1
2
3
4
5
6
7
8
9
10
from collections import OrderedDict

od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
print(od)

# Output
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

您也可以使用循环访问每个元素:

1
2
3
4
5
6
7
for key, value in od.items():
print(key, value)

# Output
a 1
b 2
c 3

可以发现,输出值的次序完全是按照我们插入的顺序决定的。

defaultdict带有默认值的字典

当我们尝试向一个python字典中取不存在的key时(d[key]),会抛出KeyError异常。
defaultdict数据结构允许我们事先传入一个数据类型,当取不存在的key时,会返回该数据类型的默认值。

1
2
3
4
5
6
7
8
9
10
11
12
from collections import defaultdict

count = defaultdict(int)
count["a"] = 1

print(count["a"])
print(count["b"])


# Output
1
0

我们预先传入int数据类型,当取不存在的键"b"时,会默认返回int类型的默认值0。

ChainMap链式字典

ChainMap用于组合多个字典或映射,返回字典列表。

1
2
3
4
5
6
7
8
9
from collections import ChainMap

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'b': 4}
chain_map = ChainMap(dict1, dict2)
print(chain_map.maps)

# Output
[{'b': 2, 'a': 1}, {'c': 3, 'b': 4}]

返回字典列表,我们可以使用d[key]方式或d.get(key)方式获取数据。

1
2
3
4
5
6
print(chain_map['a'])
print(chain_map['b'])

# Output
1
2

值得注意的是,dict1和dict2都有keyb,当我们访问组合的链式字典时chain_map['b'],返回的是索引值最小的字典中的key。

从ChainMap获取键和值
你可以通过keys()函数获得所有的键。同样,您可以使用values()函数访问所有元素的值,如下所示:

1
2
3
4
5
6
7
8
9
dict1 = { 'a' : 1, 'b' : 2 }
dict2 = { 'c' : 3, 'b' : 4 }
chain_map = ChainMap(dict1, dict2)
print (list(chain_map.keys()))
print (list(chain_map.values()))

# Output
['b', 'a', 'c']
[2, 1, 3]

向ChainMap添加新字典
如果要将新字典添加到现有ChainMap,请使用new_child()函数。

1
2
3
4
5
6
dict3 = {'e' : 5, 'f' : 6}
new_chain_map = chain_map.new_child(dict3)
print(new_chain_map)

# Output
ChainMap({'f': 6, 'e': 5}, {'a': 1, 'b': 2}, {'b': 4, 'c': 3})

总结

我向您介绍了collection模块中所有重要的数据容器,主要是提醒您在碰到适合使用它们的场景时,能够快速的想起它们,达到事半功倍的效果。
如果想要更深层次的理解它们,请阅读官方文档和其他资料。

参考资料

  1. https://docs.python.org/2/library/collections.html#module-collections
  2. https://www.digitalocean.com/community/tutorials/how-to-use-the-collections-module-in-python-3
  3. https://stackabuse.com/introduction-to-pythons-collections-module/#thedeque