美文网首页PythonPython
Python 类 - collections.abc

Python 类 - collections.abc

作者: 山药鱼儿 | 来源:发表于2021-07-17 14:57 被阅读0次

    类对于我们日常编程非常重要,编程中你会发现,我们大部分时间都在定义类。类中包含数据,并使用方法来描述数据之间的交互。因此,从某种层度上来讲,Python 中的类都是容器,用来封装属性和功能。Python 内置的容器类型:list tuple set dict,只有在子类非常简单的时,可以直接继承上述的容易类型。

    FrequencyList 子类继承自 list ,获得了 list 适用的全部 API;并且也实现了自己独有的方法 frequency ,即以字典的形式统计 list 中元素出现的频率:

    class FrequencyList(list):
        def __init__(self, nums):
            super().__init__(nums)
            
        def frequency(self):
            counts = {}
            for item in self:
                counts.setdefault(item, 0)
                counts[item] += 1
            return counts
    

    运行结果:

    >> L = FrequencyList(range(5))
    >> L
    [0, 1, 2, 3, 4]
    >> len(L)
    5
    >> L.extend(range(3))
    >> L
    [0, 1, 2, 3, 4, 0, 1, 2]
    >> L.frequency()
    {0: 2, 1: 2, 2: 2, 3: 1, 4: 1}
    

    一. 自定义容器类型

    下面,我们将以实现特殊方法的形式,来定义一个二叉树容器,并且能够像序列一样,使用下标进行访问。首先,是基类 BinaryNode 的定义:

    class BinaryNode:
        def __init__(self, left=None, right=None, value=None):
            self.left = left
            self.right = right
            self.value = value
    

    使用 tree[i] 的形式来访问二叉树中结点上的值,实际调用的即 tree.__getitem__(i)。因此,接下来定义 IndexableNode 子类,并实现 __getitem__ 方法:

    class IndexableNode(BinaryNode):
        def _traverse(self):
            if self.left is not None:
                yield from self.left._traverse()
            yield self
            if self.right is not None:
                yield from self.right._traverse()
                
        def __getitem__(self, index):
            for i, item in enumerate(self._traverse()):
                if i == index:
                    return item.value
            raise IndexError(f'Index {index} is out of range')
    

    注:_traverse 是一个递归方法,按照左侧优先,深度优先的顺序,访问结点上的值。
    实例化一个 IndexableNode 二叉树:

    >> root = IndexableNode(value=1)
    >> root.left = IndexableNode(left=IndexableNode(value=20), value=2, right=IndexableNode(value=21))
    >> root.right = IndexableNode(value=31)
    

    此时,我们就可以遍历结点、通过下标访问结点以及使用 in/not in 运算符:

    >> list(root)
    [20, 2, 21, 1, 31]
    >> root[0]
    20
    >> 20 in root
    True
    

    如果还想使用 len 计算二叉树结点的个数,则还必须实现 __len__ 方法:

    class SequenceNode(IndexableNode):
        def __len__(self):
            for count, _ in enumerate(self._traverse(), 1):
                pass
            return count
    

    实例化一个 SequenceNode 二叉树:

    >> root2 = SequenceNode(value=1)
    >> root2.left = SequenceNode(left=SequenceNode(value=20), value=2, right=SequenceNode(value=21))
    >> root2.right = SequenceNode(value=31)
    

    计算结点个数:

    >> list(root2)
    [20, 2, 21, 1, 31]
    >> len(root2)
    5
    

    但其实,实现了 __len__ 之后,SequenceNode 的功能还是不够完善,想要像内置序列类型那样使用 index count 等函数,还需要继续实现特殊方法。为此,Python 为我们提供了一个强大的工具 collections.abc 模块。

    二. collections.abc

    collections.abc 模块定义了一系列抽象基类,并提供了每一种容器类型所应具备的常用方法。从这些基类中继承了子类后,如果忘记实现某个方法,将抛出 TypeError 异常:

    from collections.abc import Sequence
    
    class BadType(Sequence):
        pass
    

    运行结果:

    >> bad_o = BadType()
    ...
    TypeError: Can't instantiate abstract class BadType with abstract methods __getitem__, __len__
    

    上文中定义的 SequenceNode 类型由于我们实现了异常中提到的两个方法 __getitem____len__,因此,如果我们定义一个全新的子类 BetterType 继承自 SequenceSequenceNode

    class BetterNode(SequenceNode, Sequence):
        pass
    

    BetterNode 创建的实例则可以像 Python 内置的序列类型一样,具备序列相关的 API:

    >> root3 = BetterNode(value=1)
    >> root3.left = BetterNode(left=BetterNode(value=20), value=2,  right=BetterNode(value=21))
    >> root3.right = BetterNode(value=31)
    

    运行结果:

    >> list(root3)
    [20, 2, 21, 1, 31]
    >> root3.count(2)
    1
    >> root3.index(21)
    2
    

    自定义容器类型时,可以从 collections.abc 模块的抽象类中继承,这些基类能够确保我们的子类具备相应的接口。

    附:对于 Set MutableMapping等更为复杂的容器类型来说,如果不继承抽象类,必须实现非常多的特殊方法,才能让自定义的类型符合 Python 编程习惯。

    相关文章

      网友评论

        本文标题:Python 类 - collections.abc

        本文链接:https://www.haomeiwen.com/subject/ttnwpltx.html