迭代器和索引可访问的类
我的类实现了一个迭代器,这样我就可以像这样使用:
for i in class():
但是我还想通过索引来访问这个类,也就是说,我希望能像访问列表那样,用数字来获取类里的某个元素:
class()[1]
我该怎么做呢?
2 个回答
目前,@Ignacio Vazquez-Abrams 的被接受的回答已经足够了。不过,其他对这个问题感兴趣的人可能想考虑从一个抽象基类(ABC
)继承他们的类(比如在标准模块 collections.abc
中找到的那些)。这样做有几个好处(可能还有其他好处):
- 确保你需要的所有方法都在,以便可以把你的对象当作“____”来使用
- 自我说明,阅读你代码的人可以立刻明白你希望你的对象“像____一样”工作。
- 允许
isinstance(myobject,SomeABC)
正常工作。 - 通常会自动提供一些方法,这样我们就不需要自己定义它们
(注意,除了上述内容,创建你自己的 ABC
还可以让你检查任何对象中是否存在特定的方法或方法集,并基于此声明该对象是 ABC
的子类,即使该对象并没有直接从 ABC
继承。更多信息请查看这个回答。)
示例:使用 ABC
实现一个只读的 list
类似的类
现在作为一个例子,让我们选择并实现一个针对原始问题中的类的 ABC
。有两个要求:
- 这个类是可迭代的
- 可以通过索引访问这个类
显然,这个类将会是某种集合。所以我们要做的是查看我们的集合 ABC
的菜单,找到合适的 ABC
(注意还有numeric
ABCs)。合适的 ABC
取决于我们希望在类中使用哪些抽象方法。
我们看到,如果我们想使用 __iter__()
方法(这对于像 for o in myobject:
这样的操作是必须的),那么我们需要一个Iterable
。但是,Iterable
不包括 __getitem__()
方法,而这个方法是我们进行 myobject[i]
这样的操作所需要的。所以我们需要使用一个不同的 ABC
。
在 collections.abc
的抽象基类菜单中,我们看到一个Sequence
是提供我们所需功能的最简单的 ABC
。而且——你看——我们得到了 Iterable
功能作为混合方法,这意味着我们不需要自己定义它——这可是免费的!我们还得到了 __contains__
、__reversed__
、index
和 count
。如果你想想,这些都是任何索引对象应该包含的东西。如果你忘记了包含它们,使用你代码的人(包括你自己!)可能会很烦(我知道我会)。
不过,还有第二个 ABC
也提供这种功能组合(可迭代的,并且可以通过 []
访问):一个Mapping
。我们应该使用哪个呢?
我们记得要求是能够通过索引访问对象(像 list
或 tuple
),也就是说不是通过键(像 dict
)。因此,我们选择 Sequence
而不是 Mapping
。
附带说明:重要的是要注意,Sequence
是只读的(Mapping
也是),所以它不允许我们做像 myobject[i] = value
或 random.shuffle(myobject)
这样的事情。如果我们想要能够做这些事情,我们需要继续查看 ABC
的菜单,使用一个MutableSequence
(或者一个MutableMapping
),这将需要实现几个额外的方法。
示例代码
现在我们可以创建我们的类了。我们定义它,并让它继承自 Sequence
。
from collections.abc import Sequence
class MyClass(Sequence):
pass
如果我们尝试使用它,解释器会告诉我们在使用之前需要实现哪些方法(注意这些方法也在 Python 文档页面上列出):
>>> myobject = MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods __getitem__, __len__
这告诉我们,如果我们继续实现 __getitem__
和 __len__
,我们就可以使用我们的新类了。我们可以这样在 Python 3 中实现:
from collections.abc import Sequence
class MyClass(Sequence):
def __init__(self,L):
self.L = L
super().__init__()
def __getitem__(self, i):
return self.L[i]
def __len__(self):
return len(self.L)
# Let's test it:
myobject = MyClass([1,2,3])
try:
for idx,_ in enumerate(myobject):
print(myobject[idx])
except Exception:
print("Gah! No good!")
raise
# No Errors!
实现 __iter__()
和 __getitem__()
这些方法,以及其他相关的方法。