collections模块包含了内建类型之外的一些有用的工具,例如Counter、defaultdict、OrderedDict、deque以及nametuple。其中Counter、deque以及defaultdict是最常用的类。
如果你想统计一个单词在给定的序列中一共出现了多少次,诸如此类的操作就可以用到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 a.most_common(3) # 频率最高的三个
Deque是一种由队列结构扩展而来的双端队列(double-ended queue),队列元素能够在队列两端添加或删除。因此它还被称为头尾连接列表(head-tail linked list),尽管叫这个名字的还有另一个特殊的数据结构实现。
Deque支持线程安全的,经过优化的append和pop操作,在队列两端的相关操作都能够达到近乎O(1)的时间复杂度。虽然list也支持类似的操作,但是它是对定长列表的操作表现很不错,而当遇到pop(0)和insert(0, v)这样既改变了列表的长度又改变其元素位置的操作时,其复杂度就变为O(n)了。
来看看相关的比较结果:
import time from collections import deque num = 100000 def append(c): for i in range(num): c.append(i) def appendleft(c): if isinstance(c, deque): for i in range(num): c.appendleft(i) else: for i in range(num): c.insert(0, i) def pop(c): for i in range(num): c.pop() def popleft(c): if isinstance(c, deque): for i in range(num): c.popleft() else: for i in range(num): c.pop(0) for container in [deque, list]: for operation in [append, appendleft, pop, popleft]: c = container(range(num)) start = time.time() operation(c) elapsed = time.time() - start print "Completed {0}/{1} in {2} seconds: {3} ops/sec".format( container.__name__, operation.__name__, elapsed, num / elapsed) # Completed deque/append in 0.0250000953674 seconds: 3999984.74127 ops/sec # Completed deque/appendleft in 0.0199999809265 seconds: 5000004.76838 ops/sec # Completed deque/pop in 0.0209999084473 seconds: 4761925.52225 ops/sec # Completed deque/popleft in 0.0199999809265 seconds: 5000004.76838 ops/sec # Completed list/append in 0.0220000743866 seconds: 4545439.17637 ops/sec # Completed list/appendleft in 21.3209998608 seconds: 4690.21155917 ops/sec # Completed list/pop in 0.0240001678467 seconds: 4166637.52682 ops/sec # Completed list/popleft in 4.01799988747 seconds: 24888.0046791 ops/sec
这个类型除了在处理不存在的键的操作之外与普通的字典完全相同。当查找一个不存在的键操作发生时,它的default_factory会被调用,提供一个默认的值,并且将这对键值存储下来。其他的参数同普通的字典方法dict()一致,一个defaultdict的实例同内建dict一样拥有同样地操作。
defaultdict对象在当你希望使用它存放追踪数据的时候很有用。举个例子,假定你希望追踪一个单词在字符串中的位置,那么你可以这么做:
from collections import defaultdict s = "the quick brown fox jumps over the lazy dog" words = s.split() location = defaultdict(list) for m, n in enumerate(words): location[n].append(m) print location # defaultdict(<type 'list'>, {'brown': [2], 'lazy': [7], 'over': [5], 'fox': [3], # 'dog': [8], 'quick': [1], 'the': [0, 6], 'jumps': [4]})
array模块定义了一个很像list的新对象类型,不同之处在于它限定了这个类型只能装一种类型的元素。array元素的类型是在创建并使用的时候确定的。
如果你的程序需要优化内存的使用,并且你确定你希望在list中存储的数据都是同样类型的,那么使用array模块很合适。举个例子,如果需要存储一千万个整数,如果用list,那么你至少需要160MB的存储空间,然而如果使用array,你只需要40MB。但虽然说能够节省空间,array上几乎没有什么基本操作能够比在list上更快。
在使用array进行计算的时候,需要特别注意那些创建list的操作。例如,使用列表推导式(list comprehension)的时候,会将array整个转换为list,使得存储空间膨胀。一个可行的替代方案是使用生成器表达式创建新的array。看代码:
import array a = array.array("i", [1,2,3,4,5]) b = array.array(a.typecode, (2*x for x in a))因为使用array是为了节省空间,所以更倾向于使用in-place操作。一种更高效的方法是使用enumerate:
import array a = array.array("i", [1,2,3,4,5]) for i, x in enumerate(a): a[i] = 2*x对于较大的array,这种in-place修改能够比用生成器创建一个新的array至少提升15%的速度。
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 portfolio = [ {'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}, {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'ACME', 'shares': 75, 'price': 115.65} ] cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price']) expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price']) print cheap # [{'price': 16.35, 'name': 'YHOO', 'shares': 45}, # {'price': 21.09, 'name': 'FB', 'shares': 200}, {'price': 31.75, 'name': 'HPQ', 'shares': 35}] print expensive # [{'price': 543.22, 'name': 'AAPL', 'shares': 50}, {'price': 115.65, 'name': 'ACME', # 'shares': 75}, {'price': 91.1, 'name': 'IBM', 'shares': 100}]
bisect模块能够提供保持list元素序列的支持。它使用了二分法完成大部分的工作。它在向一个list插入元素的同时维持list是有序的。在某些情况下,这比重复的对一个list进行排序更为高效,并且对于一个较大的list来说,对每步操作维持其有序也比对其排序要高效。
假设你有一个range集合,想添加一个range (250, 400):
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(sequence, item) => index 返回元素应该的插入点,但序列并不被修改 bisect.insort_right(a, (550, 1200)) print a # [(0, 100), (150, 220), (250, 400), (399, 450), (500, 1000), (550, 1200)]
weakref模块能够帮助我们创建Python引用,却不会阻止对象的销毁操作。这一节包含了weak reference的基本用法,并且引入一个代理类。
在开始之前,我们需要明白什么是strong reference。strong reference是一个对对象的引用次数、生命周期以及销毁时机产生影响的指针。strong reference如你所见,就是当你将一个对象赋值给一个变量的时候产生的:
>>> 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。
class Foo(object): def __init__(self): self.obj = None print 'created' def __del__(self): print 'destroyed' def show(self): print self.obj def store(self, obj): self.obj = obj a = Foo() # created b = a del a del b # destroyed >>> import weakref >>> a = Foo() created >>> b = weakref.ref(a) >>> b >>> a == b() True >>> b().show() None >>> del a destroyed >>> b() is None True若是使用weakref.proxy,就能提供相对于weakref.ref更透明的可选操作。同样是使用一个strong reference作为第一个参数并且返回一个weak reference,proxy更像是一个strong reference,但当对象不存在时会抛出异常。
>>> a = Foo() created >>> b = weakref.proxy(a) >>> b.store('fish') >>> b.show() fish >>> del a destroyed >>> b.show() Traceback (most recent call last): File "", line 1, in ? ReferenceError: weakly-referenced object no longer exists
通过shallow或deep copy语法提供复制对象的函数操作。
shallow和deep copying的不同之处在于对于混合型对象的操作(混合对象是包含了其他类型对象的对象,例如list或其他类实例)。
普通的赋值操作知识简单的将心变量指向源对象。
import copy a = [1,2,3] b = [4,5] c = [a,b] # Normal Assignment d = c print id(c) == id(d) # True - d is the same object as c print id(c[0]) == id(d[0]) # True - d[0] is the same object as c[0] # Shallow Copy d = copy.copy(c) print id(c) == id(d) # False - d is now a new object print id(c[0]) == id(d[0]) # True - d[0] is the same object as c[0] # Deep Copy d = copy.deepcopy(c) print id(c) == id(d) # False - d is now a new object print id(c[0]) == id(d[0]) # False - d[0] is now a new object
shallow copy (copy())操作创建一个新的容器,其包含的引用指向原对象中的对象。
deep copy (deepcopy())创建的对象包含的引用指向复制出来的新对象。
Pprint模块能够提供比较优雅的数据结构打印方式,如果你需要打印一个结构较为复杂,层次较深的字典或是JSON对象时,使用Pprint能够提供较好的打印结果。
假定你需要打印一个矩阵,当使用普通的print时,你只能打印出普通的列表,不过如果使用pprint,你就能打出漂亮的矩阵结构
import pprint matrix = [ [1,2,3], [4,5,6], [7,8,9] ] a = pprint.PrettyPrinter(width=20) a.pprint(matrix) # [[1, 2, 3], # [4, 5, 6], # [7, 8, 9]]