美文网首页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