具有指定长度的迭代器/生成器

Iterables / generators with specified length

可迭代对象是那些实现 __iter__ 函数的对象,其中 returns 一个迭代器对象,即提供函数 __iter____next__ 并且行为正确的对象。通常可迭代对象的大小是事先不知道的,也不期望可迭代对象知道迭代将持续多长时间;但是,在某些情况下,知道可迭代对象的长度很有价值,例如,在创建数组时。例如list(x for x in range(1000000)),创建一个初始的小数组,满了就复制,重复多次,如解释的那样。当然,在这个例子中它不是那么重要,但它说明了这一点。

对于那些事先知道长度的可迭代对象,是否有使用协议?也就是说,是否有扩展Sized and Iterable but not Collection or Reversible的协议?语言特性好像没有这个协议,有没有知名的第三方库有这样的协议?此讨论与生成器有何关系?

如果我现在理解了您的问题,那么您仍在尝试将两个不能完全以这种方式结合的概念结合起来。 generatoriterator 的子 class;这是一个过程。 len 适用于数据对象——特别是 iterable 对象,而不是遍历对象的 iterator

因此,生成器实际上并没有自己的长度。它 returns 一个值序列,并且该序列有一个长度(当生成器完成时)。您能否描述一下您对“长度生成器”的概念——如果它与我刚才描述的不同?

如果您牢记这一区别,那么是的,您可以实施 __len__ 作为 class 的扩展。您可以添加任何您喜欢的东西——例如,一个 sqrt 函数(有关详细信息,请参阅 Conway 的超现实数)。

听起来你在问 __length_hint__ 之类的问题。摘自PEP 424 – A method for exposing a length hint

CPython currently defines a __length_hint__ method on several types, such as various iterators. This method is then used by various other functions (such as list) to presize lists based on the estimate returned by __length_hint__. Types which are not sized, and thus should not define __len__, can then define __length_hint__, to allow estimating or computing a size (such as many iterators).

Being able to pre-allocate lists based on the expected size, as estimated by __length_hint__, can be a significant optimization. CPython has been observed to run some code faster than PyPy, purely because of this optimization being present.

例如,range 迭代器支持这个 (Try it online!):

it = iter(range(1000))
print(it.__length_hint__())     # prints 1000
next(it)
print(it.__length_hint__())     # prints 999

并且 list 迭代器甚至考虑了列表长度的变化 (Try it online!):

a = [None] * 10
it = iter(a)
print(it.__length_hint__())     # prints 10
next(it)
print(it.__length_hint__())     # prints 9
a.pop()
print(it.__length_hint__())     # prints 8
a.append(None)
print(it.__length_hint__())     # prints 9

Generator 迭代器不支持它,但您可以在您编写的其他迭代器中支持它。这是一个演示迭代器...

  • 生成 10,000 个元素。
  • 提示有 5,000 个元素。
  • 在每 1,000 个元素之后,它显示正在构建的列表的内存大小。
import gc

beacon = object()

class MyIterator:
    def __init__(self):
        self.n = 10_000
    def __iter__(self):
        return self
    def __length_hint__(self):
        print('__length_hint__ called')
        return 5_000
    def __next__(self):
        if self.n == 0:
            raise StopIteration
        self.n -= 1
        if self.n % 1_000 == 0:
            for obj in gc.get_objects():
                if isinstance(obj, list) and obj and obj[0] is beacon:
                    print(obj.__sizeof__())
        return beacon

list(MyIterator())

输出(Try it online!):

__length_hint__ called
45088
45088
45088
45088
45088
50776
57168
64360
72456
81560

我们看到 list 要求长度提示,并且从一开始就 pre-allocates 足够的内存用于 5,000 个引用,每个引用 8 个字节,外加 12.5% 的过度分配。在前 5,000 个元素之后,它不再要求长度提示,并不断增加其大小。

如果我的 __length_hint__ 准确地 returns 10,000,那么 list 而不是 pre-allocates 90088 字节并且一直保留到最后。