python常用库collections源码浅析

前述:

collections是python比较常用的库了,主要提供了一些比较常用的数据结构,其次为用户编写自己的python结构提供基类,不用list dict这些是因为cpython的原因,这些数据结构经常会走cpython的后门,不是纯python实现,还有就是大部分轮子原理不算难,其实研究研究对自己还是有好处的

 

数据结构

 1 __all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList',
 2             'UserString', 'Counter', 'OrderedDict', 'ChainMap']
 3 
 4 # For backwards compatibility, continue to make the collections ABCs
 5 # available through the collections module.
 6 from _collections_abc import *
 7 import _collections_abc
 8 __all__ += _collections_abc.__all__
 9 
10 from operator import itemgetter as _itemgetter, eq as _eq
11 from keyword import iskeyword as _iskeyword
12 import sys as _sys
13 import heapq as _heapq
14 from _weakref import proxy as _proxy
15 from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
16 from reprlib import recursive_repr as _recursive_repr

all里面就是所有的数据结构,这里不单独详解用法了,user前缀的是提供来做接口便利用户实现自己的数据结构,其他的是有特殊用途的数据结构

大量使用了运算符重载,所以引入eq这些模块,又比如引入heapq堆实现Counter的功能

 1 collections包含了一些特殊的容器,针对Python内置的容器,例如list、dict、set和tuple,提供了另一种选择;
 2 
 3 namedtuple,可以创建包含名称的tuple;
 4 
 5 deque,类似于list的容器,可以快速的在队列头部和尾部添加、删除元素;
 6 双端队列,支持从两端添加和删除元素。更常用的栈和队列是退化形式的双端队列,仅限于一端在输入和输出。
 7 dq = deque()
 8 dq.append(x);dq.appendleft(x)    dq.extend(seq) extendleft(seq)
 9 dq.pop() popleft()
10 dq.rotate(+-x) x队列右移x  -x 左移x
11 
12 Counter,dict的子类,计算可hash的对象;
13 Counter 支持三种形式的初始化。它的构造函数可以调用序列,一个字典包含密钥和计数,或使用关键字参数映射的字符串名称。
14 空的Counter容器可以无参数构造,并采用update()方法进行更新
15 elements()方法可以返回一个包含所有Counter数据的迭代器
16 most_common(n)返回前n个最多的数据
17 Counter实例支持聚合结果的算术和集合操作。+ - & |
18 
19 OrderedDict,dict的子类,可以记住元素的添加顺序;
20 多了一个move_to_end()  pop  popitem方法
21 
22 defaultdict,dict的子类,可以调用提供默认值的函数;
23 标准字典包括setdefault方法()获取一个值,如果值不存在,建立一个默认。相比之下,defaultdict允许调用者在初始化时预先设置默认值。
24 import collections
25 
26 def default_factory():
27     return 'default value'
28 
29 d = collections.defaultdict(default_factory, foo='bar')
30 print ('d:', d)
31 print ('foo =>', d['foo'])
32 print ('x =>', d['x'])
33 --
34 d: defaultdict(<function default_factory at 0x000002567E713E18>, {'foo': 'bar'})
35 foo => bar
36 x => default value

 

分为abc 和 init

 

 

 

abc引入collections-abc基类模块,它记录了类似mapping的类抽象方法以及基本的类方法

init中实现了OrderedDict,namedtuple,userdict。。。counter等一系列额外的数据类型,大量的利用了上述的基类模块继承以及内置函数已经实现的运算符和数据类型方法,也大量的利用slot熟悉以及yield关键字构建生成器改写py2中占用大量内存的缺点

 

 1 def move_to_end(self, key, last=True):
 2         '''Move an existing element to the end (or beginning if last==False).
 3 
 4         Raises KeyError if the element does not exist.
 5         When last=True, acts like a fast version of self[key]=self.pop(key).
 6 
 7         '''
 8         link = self.__map[key]
 9         link_prev = link.prev
10         link_next = link.next
11         soft_link = link_next.prev
12         link_prev.next = link_next
13         link_next.prev = link_prev
14         root = self.__root
15         if last:
16             last = root.prev
17             link.prev = last
18             link.next = root
19             root.prev = soft_link
20             last.next = link
21         else:
22             first = root.next
23             link.prev = root
24             link.next = first
25             first.prev = soft_link
26             root.next = link

 

 上面是ordereddict的大部分方法,还有move_to_end的实现

ordereddict利用pre和next指针重写了了字典,也就是用链表加hash的方式实现了有序字典,类似popitem delitem都是堆pre和next的简单操作,而move—to-end则根据last是不是True采用头插法或尾插法插入到dict中,所以这个数据结构天生适合用来实现LRU,一个move-to-end就解决了lru中最繁杂的操作,而类似setdefault的方法也只是利用初始化的字典,再简单的赋值即可

下面的我就不贴所有方法了,只把重要的贴出来,有兴趣自己去看看源码

def namedtuple(typename, field_names, *, verbose=False, rename=False, module=None):

    # Validate the field names.  At the user's option, either generate an error
    # message or automatically replace the field name with a valid name.
    if isinstance(field_names, str):
        field_names = field_names.replace(',', ' ').split()
    field_names = list(map(str, field_names))
    typename = str(typename)
    if rename:
        seen = set()
        for index, name in enumerate(field_names):
            if (not name.isidentifier()
                or _iskeyword(name)
                or name.startswith('_')
                or name in seen):
                field_names[index] = '_%d' % index
            seen.add(name)
    for name in [typename] + field_names:
        if type(name) is not str:
            raise TypeError('Type names and field names must be strings')
        if not name.isidentifier():
            raise ValueError('Type names and field names must be valid '
                             'identifiers: %r' % name)
        if _iskeyword(name):
            raise ValueError('Type names and field names cannot be a '
                             'keyword: %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)

    # Fill-in the class template
    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))
    )

    # Execute the template string in a temporary namespace and support
    # tracing utilities by setting a value for frame.f_globals['__name__']
    namespace = dict(__name__='namedtuple_%s' % typename)
    exec(class_definition, namespace)
    result = namespace[typename]
    result._source = class_definition
    if verbose:
        print(result._source)


    if module is None:
        try:
            module = _sys._getframe(1).f_globals.get('__name__', '__main__')
        except (AttributeError, ValueError):
            pass
    if module is not None:
        result.__module__ = module

    return result

 

namedtuple则根据传参的不同采用不同的初始化方法,类型名总是固定的字符串,后面的字段名称可以传字段空格字段的字符串形式,也可以用可迭代对象的形式 字符串会split分割后转换成list,然后就都是可迭代对象了,利用*拆包传参,再利用set构造具名数组,我们用named[index]其实就是因为具名元组内部使用for index,name in enumerate(seq)的方式给set1[index]=name 赋值构造的

def __init__(*args, **kwds):
    '''
    >>> c = Counter()                           # a new, empty counter
    >>> c = Counter('gallahad')                 # a new counter from an iterable
    >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
    >>> c = Counter(a=4, b=2)                   # a new counter from keyword args

    '''
    if not args:
        raise TypeError("descriptor '__init__' of 'Counter' object "
                        "needs an argument")
    self, *args = args
    if len(args) > 1:
        raise TypeError('expected at most 1 arguments, got %d' % len(args))
    super(Counter, self).__init__()
    self.update(*args, **kwds)


def __missing__(self, key):
    'The count of elements not in the Counter is zero.'
    # Needed so that self[missing_item] does not raise KeyError
    return 0


def most_common(self, n=None):
    '''List the n most common elements and their counts from the most
    common to the least.  If n is None, then list all element counts.

    >>> Counter('abcdeabcdabcaba').most_common(3)
    [('a', 5), ('b', 4), ('c', 3)]

    '''
    # Emulate Bag.sortedByCount from Smalltalk
    if n is None:
        return sorted(self.items(), key=_itemgetter(1), reverse=True)
    return _heapq.nlargest(n, self.items(), key=_itemgetter(1))

__missing__魔术方法用来实现当dict找不到key时要做的操作,collections大部分dict都有实现这个


然后比如我们常用的Counter的most_common这个方法,是通过堆实现的,return _heapq.nlargest(n, self.items(), key=_itemgetter(1))

上面说的大量使用运算符就以Counter为例,如果是简单的更新就用update(*,**)的方式更新计数器,函数内部会判断具体每个元素出现过没有,以此决定是新建一个键值对还是在原有的数量上+=(不是+=,我看内部好像一般不用+=都是直接赋值,可能是出于安全的考虑吧),构造了--add--、-sub等方法,新建一个counter合并或者怎样两个计数器对象,分别是self,other将他们按照方法意义将结果返回到result这个新建的计数器中,找不到指定的key当然调用missing方法,不过这只是针对结构相比字典变化大的类型,例如counter的missing是不存在的返回0,这很符合常理对吧,再比如说userdict 这种就是先用判断你有没有实现,有了用没有就抛异常

    def __getitem__(self, key):
       if key in self.data:
           return self.data[key]
       if hasattr(self.__class__, "__missing__"):
           return self.__class__.__missing__(self, key)
       raise KeyError(key)

 

而类似UserList Dict Str等用于给用户继承的类型则是大量的实现了常用方法,避免继承内置类型抄cpython近路使得一些方法不生效而编写的,当然如果你有需要可以再次重写某些方法,我们的目的也是这个

__ subclasshook__ 魔法。 这个 方法的作用是让抽象基类识别没有注册为子类的类, 你可以根据需要做 简单的或者复杂的测试——标准库的做法只是检查方法名称

继续拓展:

下面有一个继承图

image-20200407111031343

 

posted on 2020-09-25 13:30  Handso  阅读(430)  评论(0编辑  收藏  举报

导航