类对于我们日常编程非常重要,编程中你会发现,我们大部分时间都在定义类。类中包含数据,并使用方法来描述数据之间的交互。因此,从某种层度上来讲,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
继承自 Sequence
和 SequenceNode
:
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 编程习惯。
网友评论