>>> from env_helper import info; info()
页面更新时间: 2022-03-22 23:27:55
运行环境:
Linux发行版本: Debian GNU/Linux 11 (bullseye)
操作系统内核: Linux-5.10.0-11-amd64-x86_64-with-glibc2.31
Python版本: 3.9.2
9.6. 元素访问¶
虽然 __init__
无疑是你目前遇到的最重要的特殊方法,但还有不少其他的特殊方法,
让你能够完成很多很酷的任务。
本节将介绍一组很有用的魔法方法,让你能够创建行为类似于序列或映射的对象。
基本的序列和映射协议非常简单,但要实现序列和映射的所有功能,需要实现很多魔法方法。 所幸有一些捷径可走,我马上就会介绍。
注意 在Python中,协议通常指的是规范行为的规则,有点类似于接口。协议指定 应实现哪些方法以及这些方法应做什么。在Python中,多态仅仅基于对象的行为,因此这个概念很重要:其他的语言可能要求对象 属于特定的类或实现了特定的接口,而Python通常只要求对象遵循特定的协议。因此, 要成为序列,只需遵循序列协议即可。
9.6.1. 基本的序列和映射协议¶
序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需 要实现2个方法,而可变对象需要实现4个。
__len__(self)
:这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说 为键-值对数。如果__len__
返回零(且没有实现覆盖这种行为的__nonzero__
),对象在布 尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。__getitem__(self, key)
:这个方法应返回与指定键相关联的值。对序列来说,键应该是 0~n - 1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说, 键可以是任何类型。__setitem__(self, key, value)
:这个方法应以与键相关联的方式存储值,以便以后能够 使用__getitem__
来获取。当然,仅当对象可变时才需要实现这个方法。__delitem__
(self, key):这个方法在对对象的组成部分使用__del__
语句时被调用,应 删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。
对于这些方法,还有一些额外的要求。
对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。 要了解更复杂的接口和使用的抽象基类(Sequence),请参阅有关模块collections的文档。 下面来试一试,看看能否创建一个无穷序列。
>>> def check_index(key):
>>> """
>>> 指定的键是否是可接受的索引?
>>> 键必须是非负整数,才是可接受的。如果不是整数,
>>> 将引发TypeError异常;如果是负数,将引发Index
>>> Error异常(因为这个序列的长度是无穷的)
>>> """
>>> if not isinstance(key, int):
>>> raise TypeError
>>> if key < 0:
>>> raise IndexError
>>> class ArithmeticSequence:
>>> def __init__(self, start=0, step=1):
>>> """
>>> 初始化这个算术序列
>>> start -序列中的第一个值
>>> step
>>> -两个相邻值的差
>>> changed -一个字典,包含用户修改后的值
>>> """
>>> self.start = start
>>> self.step = step
>>> # 存储起始值
>>> # 存储步长值9.3 元素访问
>>> self.changed = {}
>>> # 没有任何元素被修改
>>> def __getitem__(self, key):
>>> """
>>> 从算术序列中获取一个元素
>>> """
>>> check_index(key)
>>> try:
>>> return self.changed[key]
>>> except KeyError:
>>> return self.start + key * self.step
>>> check_index(key)
>>> self.changed[key] = value
>>> def __setitem__(self, key, value):
>>> """
>>> 修改算术序列中的元素
>>> """
>>> self.changed[key] = value
这些代码实现的是一个算术序列,其中任何两个相邻数字的差都相同。第一个值是由构造函 数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。你允 许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被 修改,就使用公式self.start + key * self.step来计算它的值。
下面的示例演示了如何使用这个类:
s = ArithmeticSequence(1, 2)
s[4]
>>> s[4] = 2
>>> s[4]
>>> s[5]
请注意,我要禁止删除元素,因此没有实现 del :
>>> del s[4]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: ArithmeticSequence instance has no attribute '__delitem__'
另外,这个类没有方法__len__
,因为其长度是无穷的。
如果所使用索引的类型非法,将引发 TypeError 异常;如果索引的类型正确,但不在允许的 范围内(即为负数),将引发 IndexError 异常。
>>> s["four"]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "arithseq.py", line 31, in __getitem__
check_index(key)
File "arithseq.py", line 10, in checkIndex
>>> s[-42]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "arithseq.py", line 31, in __getitem__
check_index(key)
File "arithseq.py", line 11, in checkIndex
if key < 0: raise IndexError
IndexError
索引检查是由我为此编写的辅助函数check_index负责的。
9.6.2. 从 list、dict 和 str 派生¶
基本的序列/映射协议指定的4个方法能够让你走很远,但序列还有很多其他有用的魔法方法和普通方法。 要实现所有这些方法,不仅工作量大,而 且难度不小。 如果只想定制某种操作的行为,就没有理由去重新实现其他所有方法。
那么该如何做呢?“咒语”就是继承。在能够继承的情况下为何去重新实现呢? 在标准库中, 模块collections提供了抽象和具体的基类,但你也可以继承内置类型。 因此,如果要实现一种 行为类似于内置列表的序列类型,可直接继承list。
来看一个简单的示例——一个带访问计数器的列表。
>>> class CounterList(list):
>>> def __init__(self, *args):
>>> super().__init__(*args)
>>> self.counter = 0
>>> def __getitem__(self, index):
>>> self.counter += 1
>>> return super(CounterList, self).__getitem__(index)
CounterList 类深深地依赖于其超类( list )的行为。 CounterList
没有重写的方法(如 append 、 extend 、 index
等)都可直接使用。在两个被重写的方法中,使用 super
来调用超类的相应方法,并添加了必要的行为:初始化属性 counter (在
__init__
中)和更新属性 counter (在__getitem__
中)。
注意 重写 getitem 并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法pop。
下面的示例演示了CounterList的可能用法:
>>> cl = CounterList(range(10))
>>> cl
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> cl.reverse()
>>> cl
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> del cl[3:6]
>>> cl
[9, 8, 7, 3, 2, 1, 0]
>>> cl.counter
4
>>> cl[4] + cl[2]
12
>>> cl.counter
6
如你所见,CounterList的行为在大多数方面都类似于列表,但它有一个counter属性(其初 始值为0)。每当你访问列表元素时,这个属性的值都加1。执行加法运算cl[4] + cl[2]后,counter 的值递增两次,变成了2。
9.6.3. 其他魔法方法¶
特殊(魔法)名称的用途很多,前面展示的只是冰山一角。魔法方法大多是为非常高级的用途准备的,因此这里不详细介绍。然而,如果你感兴趣,可以模拟数字,让对象像函数一样被调用,影响对象的比较方式,等等。