Contents
简介
collections 库包含两个文件 collections.py 和 _abcoll.py 。
至于为什么要拆分成两个文件, collections 库在文件一开始就在注释里写到:
For bootstrapping reasons, the collection ABCs are defined in _abcoll.py. They should however be considered an integral part of collections.py.
我刚开始找不出它这个 bootstrapping reasons, 后来 google 了一下,发现这个 bootstrapping reasons 的解释。
不过这个链接里说 collections 会引用 os , 而 os 又会引用 _abcoll ,如果 collections 和 _abcoll 的代码在一起,会造成循环引用。
但是我代码看了头天也没发现 collections.py 文件中有 import os 的地方。
好吧,这个问题咱先不追究了,还是分析主要代码要紧。
Tip
deque, defaultdict 这两个类是用 C 实现的,暂且不表。
OrderedDict
OrderedDict ,一看名字就知道,它会记住每个 Key 的插入顺序。当然,它还是一个 dict ,所以从 dict 继承也是理所当然的。
它只从 dict 类继承了 __getitem__ 、 __len__ 、 __contains__ 和 get 。其它的操作都是顺序敏感的,所以需要重载。
它的时间复杂度还是和 dict 是一样的。
具体实现思路是 :
用双端循环链表来保存 Key 值 , 其中每个节点为 [PREV, NEXT, KEY], 该链表初始化时有一个哨兵节点 , 该节点永远不会被删除 ( 这样算法实现起来简单一点 ).
使用 self.__map 来保存 Key 值和对应的双端循环链表中的节点的映射关系 .
使用继承的 dict 功能来保存 Key 和对应 Value 的映射关系 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | class OrderedDict(dict):
def __init__(self, *args, **kwds):
""" 这里虽然提供了 `kwds` ,但是不建议传入多个命名参数
因为这些参数的顺序是随意的,不是按照你传入的顺序来的
"""
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
# 哨兵元素
self.__root = root = []
# 双端循环链表
root[:] = [root, root, None]
self.__map = {}
self.__update(*args, **kwds)
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
'od.__setitem__(i, y) <==> od[i]=y'
if key not in self:
# 向链表中插入新的 Key 节点
# self.__map 中添加 Key 和 Key 节点的映射关系
root = self.__root
last = root[0]
last[1] = root[0] = self.__map[key] = [last, root, key]
# 使用继承的 dict 功能来保存 Key 和对应 Value 的映射关系
return dict_setitem(self, key, value)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
dict_delitem(self, key)
# 先从映射关系中根据 Key 找到 Key 节点 , 然后再删除双端循环列表对应的 Key 节点
link_prev, link_next, _ = self.__map.pop(key)
link_prev[1] = link_next # update link_prev[NEXT]
link_next[0] = link_prev # update link_next[PREV]
def __iter__(self):
'od.__iter__() <==> iter(od)'
# Traverse the linked list in order.
root = self.__root
curr = root[1] # start at the first node
while curr is not root:
yield curr[2] # yield the curr[KEY]
curr = curr[1] # move to next node
def __reversed__(self):
'od.__reversed__() <==> reversed(od)'
# Traverse the linked list in reverse order.
root = self.__root
curr = root[0] # start at the last node
while curr is not root:
yield curr[2] # yield the curr[KEY]
curr = curr[0] # move to previous node
def clear(self):
'od.clear() -> None. Remove all items from od.'
root = self.__root
root[:] = [root, root, None]
self.__map.clear()
dict.clear(self)
# 以下的操作方法 , 都不依赖于对链表和 self.__map 的操作了
# 可以说上面那些方法是底层 API, 下面的是高层 API
# 这些方法的代码也不全部列出来的,都比较简单
... ...
update = MutableMapping.update
# 再命名一个 `__update` 是专门给 __init__ 用的
# 不然,用户继承该类并重载了 __update,就对该类产生了破坏
__update = update
# 该 Object 实例在 Python 中是独一无二的
__marker = object()
... ...
def __reduce__(self):
可以让 Pickle 进行序列化和反序列化
items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
for k in vars(OrderedDict()):
inst_dict.pop(k, None)
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
... ...
|
其实完全可以将双端循环链表这个概念单独抽来出来做为一个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | class DoubleLink(object):
""" 双端循环链表 """
def __init__(self):
super(DoubleLink, self).__init__()
self.__root = root = []
root[:] = [root, root, None]
self.__map = {}
def add(self, key):
# 每次都是在哨兵节点的前面插入一个新的节点
# 每次插入的节点逻辑上为最后一个节点
root = self.__root
last = root[0]
last[1] = root[0] = self.__map[key] = [last, root, key]
def delete(self, value):
prev, next, _ = self.__map.pop(key)
prev[1] = next
next[0] = prev
def __iter__(self):
root = self.__root # 哨兵节点不用参与遍历
curr = root[1] # start at the first node
while curr is not root:
yield curr[2] # yield the curr[KEY]
curr = curr[1] # move to next node
def __reversed__(self):
root = self.__root # 哨兵节点不用参与遍历
curr = root[0] # start at the last node
while curr is not root:
yield curr[2] # yield the curr[KEY]
curr = curr[0] # move to previous node
def clear(self):
root = self.__root
# 这一行 , 猛一看还以为写错了
# 后来仔细想了一下 , [root, root, None] 中的 root 其实是一个引用 ( 引用本身不保存任何值 , 相当于指针 )
# 当赋值给 root[:] 时 , 直接清除了 root 中原来保存的值
# 直接变成 [root 引用 , root 引用 , None]
root[:] = [root, root, None]
self.__map.clear()
|
这样,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 25 26 | class OrderedDict(dict):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
self.__dl = DoubleLink()
self.__update(*args, **kwds)
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
if key not in self:
self.__dl.add(key)
return dict_setitem(self, key, value)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
dict_delitem(self, key)
self.__dl.delete(key)
__iter__ = self.__dl.__iter__
__reversed__ = self.__dl.__reversed__
def clear(self):
self.__dl.clear()
dict.clear(self)
... ...
|
namedtuple
之前在用 namedtuple 时就奇怪 , 这个家伙不是一个类么 , 怎么没有首字母大写 . 后来看了一下代码 , 原来实现成函数了 .
官方文档对其介绍直接了当 :
factory function for creating tuple subclasses with named fields `namedtuple` 是创建附带命名字段的 tuple 子类的工厂方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | _class_template = '''\
class {typename}(tuple): # 这里的 tuple 很奇怪,为什么不用 _tuple
'{typename}({arg_list})'
# 避免自动从 tuple 继承 __dict__, 减少内存消耗
__slots__ = ()
_fields = {field_names!r}
def __new__(_cls, {arg_list}):
'Create new instance of {typename}({arg_list})'
return _tuple.__new__(_cls, ({arg_list}))
@classmethod
def _make(cls, iterable, new=tuple.__new__, len=len): # 这里的 tuple 也是
'Make a new {typename} object from a sequence or iterable'
result = new(cls, iterable)
if len(result) != {num_fields:d}:
raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result))
return result
def __repr__(self):
'Return a nicely formatted representation string'
return '{typename}({repr_fmt})' % self
def _asdict(self):
'Return a new OrderedDict which maps field names to their values'
return OrderedDict(zip(self._fields, self))
def _replace(_self, **kwds):
'Return a new {typename} object replacing specified fields with new values'
result = _self._make(map(kwds.pop, {field_names!r}, _self))
if kwds:
raise ValueError('Got unexpected field names: %r' % kwds.keys())
return result
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return tuple(self)
__dict__ = _property(_asdict)
def __getstate__(self):
'Exclude the OrderedDict from pickling'
pass
{field_defs}
'''
_repr_template = '{name}=%r'
_field_template = '''\
{name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')
'''
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | def namedtuple(typename, field_names, verbose=False, rename=False):
# filed_names 支持两种形式:
# 1. 以,号或者空白字符分割的字符串 , "x, y, z", "x y z" ... ...
# 2. 生成器
if isinstance(field_names, basestring):
field_names = field_names.replace(',', ' ').split()
field_names = map(str, field_names)
# 有些字段重名 /Python 关键字 / 命名不规范等等,需要重新命名
# 打开 rename 参数,就会对这些情况进行重新命名
# 多用于自动生成 namedtuple 的情况,比如自从从数据库中获取数据
if rename:
seen = set()
for index, name in enumerate(field_names):
if (not all(c.isalnum() or c=='_' for c in name)
or _iskeyword(name)
or not name
or name[0].isdigit()
or name.startswith('_')
or name in seen):
field_names[index] = '_%d' % index
seen.add(name)
# 还是对字段名进行合法性校验
for name in [typename] + field_names:
if not all(c.isalnum() or c=='_' for c in name):
raise ValueError('Type names and field names can only contain '
'alphanumeric characters and underscores: %r' % name)
if _iskeyword(name):
raise ValueError('Type names and field names cannot be a '
'keyword: %r' % name)
if name[0].isdigit():
raise ValueError('Type names and field names cannot start with '
'a number: %r' % name)
seen = set()
for name in field_names:
if name.startswith('_') and not rename:
raise ValueError('Field names cannot start with an underscore: '
'%r' % name)
if name in seen:
raise ValueError('Encountered duplicate field name: %r' % name)
seen.add(name)
# 生成类的定义
class_definition = _class_template.format(
typename = typename,
field_names = tuple(field_names),
num_fields = len(field_names),
arg_list = repr(tuple(field_names)).replace("'", "")[1:-1],
repr_fmt = ', '.join(_repr_template.format(name=name)
for name in field_names),
field_defs = '\n'.join(_field_template.format(index=index, name=name)
for index, name in enumerate(field_names))
)
if verbose: # 用于调试
print class_definition
# Execute the template string in a temporary namespace and support
# tracing utilities by setting a value for frame.f_globals['__name__']
# 在一个新的命名空间里创建 namedtuple,应该是为了安全考虑
namespace = dict(
_itemgetter=_itemgetter,
__name__='namedtuple_%s' % typename, # 指定 namedtuple.__module__ 的值
OrderedDict=OrderedDict,
_property=property,
_tuple=tuple,
)
try:
exec class_definition in namespace
except SyntaxError as e:
raise SyntaxError(e.message + ':\n' + class_definition)
result = namespace[typename]
# _sys._getframe(1): 获取创建 namedtuple 的栈帧
# 并将该栈帧的名称赋值指给该 namedtuple 的 __module__,
# 让 pickle 模块在序列化时能找到该 namedtuple 所在的模块
# Jython 未定义 sys._getframe,
# IronPython 虽然定义了 sys._getframe 但是参数只能为 0(即当前栈帧)
try:
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return result
|
如果有谁奇怪 namedtuple 的实现方式的话,可以看一下官方 issue 列表:
collections.namedtuple uses exec to create new classes
里面可能有你想要的答案,反正我看了还是不理解这种奇怪的实现方式。
当然除了上面的官方 issue 列表链接中,有人贴出来了 diff 来展示不用 exec 的实现方式。
这有这个 地址 ,也实现了另外一种不用 exec 来实现 namedtuple 的方式。
Counter
在其它语言中叫做 MultiSet(C++)或者 Bag(Lua),翻译为多重集合。
相当于集合的扩展版本,集合是不允许内部有相同元素的,但是多重集合允许。
当然 Python 的这个 Counter,在功能上来说,比一般的多重集合还更为强大一点,它是兼有多重集合和字典的特性。
基本了解到 Counter 是什么,下面的代码其实就不用怎么看了,没有什么比较复杂的代码,所以有些我直接省略掉了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class Counter(dict):
def __init__(self, iterable=None, **kwds):
super(Counter, self).__init__()
self.update(iterable, **kwds)
def __missing__(self, key):
""" 如果 Counter()[key] 中的 key 不存在,则返回 0"""
return 0
def most_common(self, n=None):
""" 列出最多的键值对,n 参数指定列出最多的前几个键值对 """
if n is None:
return sorted(self.iteritems(), key=_itemgetter(1), reverse=True)
return _heapq.nlargest(n, self.iteritems(), key=_itemgetter(1))
def elements(self):
""" 列出多重集合中的所有元素,重复的元素会输出多个 """
return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
@classmethod
def fromkeys(cls, iterable, v=None):
# 由于从 dict 继承过来,但是 Counter 又不支持该操作,就直接重载抛出异常
raise NotImplementedError(
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
def update(self, iterable=None, **kwds):
""" 类似于 dict.update,没什么好讲的 """
... ...
def subtract(self, iterable=None, **kwds):
""" 也类似于 dcit.update, 不过操作的逻辑是相反的 """
... ...
def copy(self):
""" 复制出自己一份,也是 dict 本身就有的方法,重载一下 """
'Return a shallow copy.'
return self.__class__(self)
def __reduce__(self):
""" 能够让 pickle 进行序列化和反序列化 """
return self.__class__, (dict(self),)
def __delitem__(self, elem):
if elem in self: # 如果 Key 值不存在,不需要抛出异常
super(Counter, self).__delitem__(elem)
def __repr__(self):
... ...
# 重载 Counter 的运行操作,支持相加、相减、交集、并集
# 代码就不列出来了
... ...
|
_abcoll.py
该模块里面代码都是比较简单的。
一种用来提供 isinstance 判断某种类属于哪种类型。
本质上就是判断某个类有没有特定的方法, 但是使用 isinstance 来判断,比较优雅且明了。
另外一种是用来提供 mixin 功能的。
有关该模块的详细方法可阅读 collections 官方文档