迭代器和索引可访问的类

68 投票
2 回答
75139 浏览
提问于 2025-04-16 14:00

我的类实现了一个迭代器,这样我就可以像这样使用:

for i in class():

但是我还想通过索引来访问这个类,也就是说,我希望能像访问列表那样,用数字来获取类里的某个元素:

class()[1]

我该怎么做呢?

2 个回答

106

目前,@Ignacio Vazquez-Abrams 的被接受的回答已经足够了。不过,其他对这个问题感兴趣的人可能想考虑从一个抽象基类(ABC继承他们的类(比如在标准模块 collections.abc中找到的那些)。这样做有几个好处(可能还有其他好处):

  • 确保你需要的所有方法都在,以便可以把你的对象当作“____”来使用
  • 自我说明,阅读你代码的人可以立刻明白你希望你的对象“像____一样”工作。
  • 允许 isinstance(myobject,SomeABC) 正常工作。
  • 通常会自动提供一些方法,这样我们就不需要自己定义它们

(注意,除了上述内容,创建你自己的 ABC 还可以让你检查任何对象中是否存在特定的方法或方法集,并基于此声明该对象是 ABC 的子类,即使该对象并没有直接从 ABC 继承更多信息请查看这个回答。


示例:使用 ABC 实现一个只读的 list 类似的类

现在作为一个例子,让我们选择并实现一个针对原始问题中的类的 ABC。有两个要求:

  1. 这个类是可迭代的
  2. 可以通过索引访问这个类

显然,这个类将会是某种集合。所以我们要做的是查看我们的集合 ABC 的菜单,找到合适的 ABC(注意还有numeric ABCs)。合适的 ABC 取决于我们希望在类中使用哪些抽象方法。

我们看到,如果我们想使用 __iter__() 方法(这对于像 for o in myobject: 这样的操作是必须的),那么我们需要一个Iterable。但是,Iterable 不包括 __getitem__() 方法,而这个方法是我们进行 myobject[i] 这样的操作所需要的。所以我们需要使用一个不同的 ABC

collections.abc 的抽象基类菜单中,我们看到一个Sequence 是提供我们所需功能的最简单的 ABC。而且——你看——我们得到了 Iterable 功能作为混合方法,这意味着我们不需要自己定义它——这可是免费的!我们还得到了 __contains____reversed__indexcount。如果你想想,这些都是任何索引对象应该包含的东西。如果你忘记了包含它们,使用你代码的人(包括你自己!)可能会很烦(我知道我会)。

不过,还有第二个 ABC 也提供这种功能组合(可迭代的,并且可以通过 [] 访问):一个Mapping。我们应该使用哪个呢?

我们记得要求是能够通过索引访问对象(像 listtuple),也就是说不是通过键(像 dict)。因此,我们选择 Sequence 而不是 Mapping


附带说明:重要的是要注意,Sequence 是只读的(Mapping 也是),所以它不允许我们做像 myobject[i] = valuerandom.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!

它可以工作!

70

实现 __iter__()__getitem__() 这些方法,以及其他相关的方法。

撰写回答