http://blog.jobbole.com/65218/ 转载的博客
数据结构
数据结构的概念很好理解,就是用来将数据组织在一起的结构。换句话说,数据结构是用来存储一系列关联数据的东西。在Python中有四种内建的数据结构,分别是List、Tuple、Dictionary以及Set。大部分的应用程序不需要其他类型的数据结构,但若是真需要也有很多高级数据结构可供选择,例如Collection、Array、Heapq、Bisect、Weakref、Copy以及Pprint。本文将介绍这些数据结构的用法,看看它们是如何帮助我们的应用程序的。
关于四种内建数据结构的使用方法很简单,并且网上有很多参考资料,因此本文将不会讨论它们。
1. Collections
collections模块包含了内建类型之外的一些有用的工具,例如Counter、defaultdict、OrderedDict、deque以及nametuple。其中Counter、deque以及defaultdict是最常用的类。
1.1 Counter()
如果你想统计一个单词在给定的序列中一共出现了多少次,诸如此类的操作就可以用到Counter。来看看如何统计一个list中出现的item次数:
from
collections
import
Counter
li
=
[
"Dog"
,
"Cat"
,
"Mouse"
,
42
,
"Dog"
,
42
,
"Cat"
,
"Dog"
]
a
=
Counter
(
li
)
print
a
# Counter({'Dog': 3, 42: 2, 'Cat': 2, 'Mouse': 1})
print
len
(
set
(
li
)
)
# 4
|
以下的代码片段找出一个字符串中出现频率最高的单词,并打印其出现次数。
1.2 Deque
Deque是一种由队列结构扩展而来的双端队列(double-ended queue),队列元素能够在队列两端添加或删除。因此它还被称为头尾连接列表(head-tail linked list),尽管叫这个名字的还有另一个特殊的数据结构实现。
Deque支持线程安全的,经过优化的append和pop操作,在队列两端的相关操作都能够达到近乎O(1)的时间复杂度。虽然list也支持类似的操作,但是它是对定长列表的操作表现很不错,而当遇到pop(0)和insert(0, v)这样既改变了列表的长度又改变其元素位置的操作时,其复杂度就变为O(n)了。
来看看相关的比较结果:
译者注:rotate是队列的旋转操作,Right rotate(正参数)是将右端的元素移动到左端,而Left rotate(负参数)则相反。
1.3 Defaultdict
这个类型除了在处理不存在的键的操作之外与普通的字典完全相同。当查找一个不存在的键操作发生时,它的default_factory会被调用,提供一个默认的值,并且将这对键值存储下来。其他的参数同普通的字典方法dict()一致,一个defaultdict的实例同内建dict一样拥有同样地操作。
defaultdict对象在当你希望使用它存放追踪数据的时候很有用。举个例子,假定你希望追踪一个单词在字符串中的位置,那么你可以这么做:
2. Array
array模块定义了一个很像list的新对象类型,不同之处在于它限定了这个类型只能装一种类型的元素。array元素的类型是在创建并使用的时候确定的。
如果你的程序需要优化内存的使用,并且你确定你希望在list中存储的数据都是同样类型的,那么使用array模块很合适。举个例子,如果需要存储一千万个整数,如果用list,那么你至少需要160MB的存储空间,然而如果使用array,你只需要40MB。但虽然说能够节省空间,array上几乎没有什么基本操作能够比在list上更快。
在使用array进行计算的时候,需要特别注意那些创建list的操作。例如,使用列表推导式(list comprehension)的时候,会将array整个转换为list,使得存储空间膨胀。一个可行的替代方案是使用生成器表达式创建新的array。看代码:
对于较大的array,这种in-place修改能够比用生成器创建一个新的array至少提升15%的速度。
那么什么时候使用array呢?是当你在考虑计算的因素之外,还需要得到一个像C语言里一样统一元素类型的数组时。
3. Heapq
heapq模块使用一个用堆实现的优先级队列。堆是一种简单的有序列表,并且置入了堆的相关规则。
堆是一种树形的数据结构,树上的子节点与父节点之间存在顺序关系。二叉堆(binary heap)能够用一个经过组织的列表或数组结构来标识,在这种结构中,元素N的子节点的序号为2*N+1和2*N+2(下标始于0)。简单来说,这个模块中的所有函数都假设序列是有序的,所以序列中的第一个元素(seq[0])是最小的,序列的其他部分构成一个二叉树,并且seq[i]节点的子节点分别为seq[2*i+1]以及seq[2*i+2]。当对序列进行修改时,相关函数总是确保子节点大于等于父节点。
import
heapq
heap
=
[
]
for
value
in
[
20
,
10
,
30
,
50
,
40
]
:
heapq
.
heappush
(
heap
,
value
)
while
heap
:
print
heapq
.
heappop
(
heap
)
|
4. Bisect
bisect模块能够提供保持list元素序列的支持。它使用了二分法完成大部分的工作。它在向一个list插入元素的同时维持list是有序的。在某些情况下,这比重复的对一个list进行排序更为高效,并且对于一个较大的list来说,对每步操作维持其有序也比对其排序要高效。
假设你有一个range集合:
a = [ ( 0 , 100 ) , ( 150 , 220 ) , ( 500 , 1000 ) ] 如果我想添加一个range (250, 400),我可能会这么做:
bisect(sequence, item) => index 返回元素应该的插入点,但序列并不被修改。
import
bisect
a
=
[
(
0
,
100
)
,
(
150
,
220
)
,
(
500
,
1000
)
]
bisect
.
insort_right
(
a
,
(
250
,
400
)
)
bisect
.
insort_right
(
a
,
(
399
,
450
)
)
print
a
# [(0, 100), (150, 220), (250, 400), (500, 1000)]
print
bisect
.
bisect
(
a
,
(
550
,
1200
)
)
# 5
bisect
.
insort_right
(
a
,
(
550
,
1200
)
)
print
a
# [(0, 100), (150, 220), (250, 400), (399, 450), (500, 1000), (550, 1200)]
|
新元素被插入到第5的位置。
5. Weakref
weakref模块能够帮助我们创建Python引用,却不会阻止对象的销毁操作。这一节包含了weak reference的基本用法,并且引入一个代理类。
在开始之前,我们需要明白什么是strong reference。strong reference是一个对对象的引用次数、生命周期以及销毁时机产生影响的指针。
strong reference如你所见,就是当你将一个对象赋值给一个变量的时候产生的:
2 |
>>>
a
=
[
1
,
2
,
3
]
>>>
b
=
a
|
在这种情况下,这个列表有两个strong reference,分别是a和b。在这两个引用都被释放之前,这个list不会被销毁。
Weak reference则是对对象的引用计数器不会产生影响。当一个对象存在weak reference时,并不会影响对象的撤销。这就说,如果一个对象仅剩下weak reference,
那么它将会被销毁。
你可以使用weakref.ref函数来创建对象的weak reference。这个函数调用需要将一个strong reference作为第一个参数传给函数,
并且返回一个weak reference。
4
5
|
>>>
import
weakref
>>>
a
=
Foo
(
)
created
>>>
b
=
weakref
.
ref
(
a
)
>>>
b
|
一个临时的strong reference可以从weak reference中创建,即是下例中的b():
3
4
|
>>>
a
==
b
(
)
True
>>>
b
(
)
.
show
(
)
None
|
proxy更像是一个strong reference,但当对象不存在时会抛出异常。