如何让list()在不调用__len__的情况下使用__iter__?

bfo*_*ine 16 python

我有一个同时具有a __iter____len__方法的类.后者使用前者来计算所有元素.

它的工作原理如下:

class A:
    def __iter__(self):
        print("iter")
        for _ in range(5):
            yield "something"

    def __len__(self):
        print("len")
        n = 0
        for _ in self:
            n += 1
        return n
Run Code Online (Sandbox Code Playgroud)

现在,如果我们采取例如它打印实例的长度leniter,如预期:

>>> len(A())
len
iter
5
Run Code Online (Sandbox Code Playgroud)

但是,如果我们调用list()它同时呼吁__iter____len__:

>>> list(A())
len
iter
iter
['something', 'something', 'something', 'something', 'something']
Run Code Online (Sandbox Code Playgroud)

如果我们生成一个生成器表达式,它按预期工作:

>>> list(x for x in A())
iter
['something', 'something', 'something', 'something', 'something']
Run Code Online (Sandbox Code Playgroud)

我会假设list(A())list(x for x in A())工作相同,但他们没有.

请注意,它似乎先打电话__iter__,然后__len__,然后遍历迭代器:

class B:
    def __iter__(self):
        print("iter")

        def gen():
            print("gen")
            yield "something"

        return gen()

    def __len__(self):
        print("len")
        return 1

print(list(B()))
Run Code Online (Sandbox Code Playgroud)

输出:

iter
len
gen
['something']
Run Code Online (Sandbox Code Playgroud)

我怎么能list()不调用,__len__以便我的实例的迭代器不被消耗两次?我可以定义例如一个length或一个size方法,然后一个人会调用,A().size()但那不是pythonic.

我试图计算长度__iter__并对其进行缓存,以便后续调用__len__不需要再次list()调用,而是调用__len__而不开始迭代,因此它不起作用.

请注意,在我的情况下,我处理非常大的数据集合,因此不能选择缓存所有项目.

agh*_*ast 11

可以肯定的是,list()构造函数检测到len()可用并调用它以便为列表预先分配存储空间.

你的实现几乎完全倒退了.您正在__len__()使用__iter__(),而不是Python期望的实现.期望是提前len()确定长度的快速,有效的方法.

我认为你不能说服list(A())不要打电话len.正如您已经观察到的那样,您可以创建一个阻止len被调用的中间步骤.

如果序列是不可变的,你肯定应该缓存结果.如果您推测的项目数量太多,那么计算len不止一次就没有意义.

  • 轶事:我曾经将“__len__”实现为“return len(list(iter(self)))”,当我的测试覆盖率跟踪停止工作时,发现这是一个非常糟糕的主意。事实证明,“list(foo)”调用“__len__”,“__len__”又调用“list()”,后者又调用“__len__”等,直到出现 MaximumRecursionError——它会关闭覆盖率跟踪——然后“list()”会抑制该错误。错误并假设 `__len__` 不可用。缓慢且有意想不到的副作用! (3认同)