Python数据结构进阶

每一篇Python基础教程中,几乎都会提到list,set,dict等内置数据结构。除此之外,Python还有一些进阶数据结构,它们大部分储存在collections库中,除此之外 还有array.array等数据结构。你可以将它们与C++的STL相比较。

这些高级数据结构在第三方库中也有着广泛应用。例如,在pytorch中,我们可以用collections.OrderedDict来建立一个神经网络,并为每一层网络命名:

1
2
3
4
5
6
model = nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(1,20,5)),
          ('relu1', nn.ReLU()),
          ('conv2', nn.Conv2d(20,64,5)),
          ('relu2', nn.ReLU())
        ]))

接下来,本文将重点介绍几种较为常见的高级数据结构,想要了解更多,可以访问https://docs.python.org/3.8/library/collections

collections.namedtuple

我们知道,元组(tuple)可以将不同类型的变量组合在一起,就像(1,'A',[0.1,0.2])这样。从某种意义上,元组和C++中的结构体(struct)在应用上有一定的相似之处。但元组本身不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义。collections.namedtuple解决了这一问题。

该数据结构的定义方式如下:

1
2
3
collections.namedtuple(typename, field_names)
# typename: 元组名称
# field_name: 元组中元素的名称,可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。

更多用法见以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#! /usr/bin/python3
from collections import namedtuple

# 新建namedtuple
Course = namedtuple('Course', ['name', 'credits', 'time'])

# 获取namedtuple的属性
print(Course._fields)

# 实例化Course
Linear_Algebra = Course('linear algebra',4,('Tue','13:30-15:05'))
print(Linear_Algebra)

# 获取属性
print(Linear_Algebra.time)

# 以字典的方式呈现namedtuple(严格地来说是OrderedDict,下文会讲)
print(Linear_Algebra._asdict())

运行结果如下:

1
2
3
4
('name', 'credits', 'time')
Course(name='linear algebra', credits=4, time=('Tue', '13:30-15:05'))
('Tue', '13:30-15:05')
{'name': 'linear algebra', 'credits': 4, 'time': ('Tue', '13:30-15:05') }

collections.OrderedDict

众所周知,Python中的dict是基于hash table实现的(详细的机制可以浏览Fluent Python一书),这使得dict在实现高性能查找的同时,舍弃了有序性(Python3.6之前)。而collections.OrderedDict在添加键的时候会保持顺序,保证了键的迭代次序的一致性。

其用法见以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#! /usr/bin/python3
from collections import OrderedDict

print("Before deleting:")
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
od['d'] = 4
od['e'] = 5
od['f'] = 6

for key, value in od.items():
	print(key, value)

print("After deleting:")
od.pop('c') # 删除操作
for key, value in od.items():
	print(key, value)

print("After re-inserting:")
od['c'] = 3 # 赋值操作
for key, value in od.items():
	print(key, value)

运行结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Before deleting:
a 1
b 2
c 3
d 4
e 5
f 6
After deleting:
a 1
b 2
d 4
e 5
f 6
After re-inserting:
a 1
b 2
d 4
e 5
f 6
c 3

可以看到,OrderedDict会根据放入元素的先后顺序进行排序。除此之外,OrderedDictdict并无太大区别。

注:在Python3.6及之后的版本,所有的普通dict都变为有序的了,故两者将无区别。

collections.defaultdict

在使用字典时,如果访问字典中不存在的键值对,程序会报错,就像这样:

1
2
GPAdict = {'A+':4.0,'A':4.0}
print(GPAdict['A-'])
1
2
3
4
Traceback (most recent call last):
  File "/home/nullptr/open-source/advanced_python/1_advanced_data_structure/defaultdict_demo.py", line 2, in <module>
    print(GPAdict['A-'])
KeyError: 'A-'

collections.defaultdict可以解决这一问题。它在创建时可以传入一个可调用对象(可以是一般函数,也可以是lambda函数等),作为字典中没有键时的默认选择。

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

GPAdict = defaultdict(lambda:4.0)
GPAdict['A+'] = 4.0
GPAdict['A'] = 4.0
print(GPAdict['B+'])

# 以下写法也可以
'''
def foo():
    return 4.0
GPAdict = defaultdict(foo)
GPAdict['A+'] = 4.0
GPAdict['A'] = 4.0
print(GPAdict['B+'])
'''

运行结果如下:

1
4.0
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
visitors: total visits: time(s) reads: time(s)