PEP 424 __length_hint __() - 有没有办法对发电机或拉链做同样的事情?

时间:2016-07-14 22:41:28

标签: python python-3.x generator python-internals

刚刚从PEP 424(https://www.python.org/dev/peps/pep-0424/)遇到了这个非常棒的__length_hint__()迭代器方法。哇!一种获取迭代器长度而不会耗尽迭代器的方法。

我的问题:

  1. 有一个简单的解释,这个魔法是如何工作的?我只是好奇。
  2. 是否有限制和案例无法运作? ("提示"听起来有点可疑)。
  3. 有没有办法获得拉链和发电机的提示?或者它只是迭代器的基础?
  4. 编辑:顺便说一句,我看到__length__hint__()从当前位置计算到最后。即部分消耗的迭代器将报告剩余长度。有趣。

4 个答案:

答案 0 :(得分:6)

  

哇!一种获取迭代器长度而不会耗尽迭代器的方法。

否。这是一种获得模糊提示的方式,可以了解长度。没有要求它以任何方式准确。

  

有一个简单的解释,这个魔法是如何工作的?

迭代器实现了一个__length_hint__方法,该方法使用某种特定于迭代器的信息来猜测它将输出多少个元素。这个猜测可能相当不错,或者它可能非常糟糕。例如,列表迭代器知道它在列表中的位置以及列表的长度,因此它可以报告列表中剩余的元素数。

  

是否存在无法运作的限制和案例?

如果迭代器没有足够的信息来猜测它何时会耗尽,那么它就无法实现有用的__length_hint__。例如,这就是为什么发电机没有发电机的原因。无限迭代器也无法实现有用的__length_hint__,因为没有办法发出无限长的信号。

  

有没有办法获得拉链和发电机的提示?或者它只是迭代器的基础?

zip实例和生成器都是各种迭代器。但是,zip和生成器类型都不提供__length_hint__方法。

答案 1 :(得分:1)

这样做的目的基本上只是为了在Cython / C代码中促进更高效的内存分配。例如,假设一个Cython模块公开了一个带有可迭代自定义MyNetworkConnection()对象的函数,并且在内部需要为数据结构创建和分配内存以在Cython / C代码中表示它们。如果我们可以对迭代器中的项目数进行粗略估计,我们可以在一次操作中分配足够大的内存块,以便在调整大小的情况下容纳所有这些内存。

如果实现__len__(),我们知道确切的长度,并可以将其用于内存分配。但通常我们实际上并不知道确切的长度,所以这个估计可以通过给我们一个“棒球场”来帮助我们提高性能。

它在纯Python代码中也很有用,例如,可能是面向用户的操作完成时间估计值?

对于问题2,嗯,这是一个暗示,所以你不能完全依赖它。如果提示太低,您仍必须考虑分配新内存,如果提示太高,则必须清理。我个人并不了解其他限制或潜在问题。

对于问题3,我认为它没有理由不适用于Generators,因为Generator 是Iterator

>>> import collections
>>> def my_generator(): yield
>>> gen = my_generator()
>>> isinstance(gen, collections.Iterator)
True

答案 2 :(得分:1)

这个问题有几个答案,但他们略微忽略了这一点:__length_hint__不是魔术。这是一个协议。如果一个对象没有实现协议,那就是它。

让我们绕道而行,看看a + b,因为这是一个简单的例子。 +运算符依赖于a.__add__b.__radd__来实际执行某些操作。 int实现__add__表示算术添加(1 + 2 == 3),而list实现__add__表示内容连接({{1} }})。这是因为[1] + [2] == [1, 2]只是一个协议,如果它们提供它,对象必须遵守这个协议。 __add__的定义基本上只是"取另一个操作数并返回一个对象"。

__add__没有单独的,普遍的含义。如果操作数不提供+__add__,那么python就无法做到这一点。

回到实际问题,这意味着什么?

  

有一个简单的解释,这个魔法是如何工作的?我只是好奇。

PEP 424中列出了所有魔法,但它基本上是:尝试_radd__,回退到len(obj),使用默认值。那是全部的魔力。

实际上,一个对象必须根据它对自身的了解来实现obj.__length_hint__。例如,取range backportPy3.6 C Code__length_hint__

range_iterator

这里,迭代器知道它最多有多长,以及它提供了多少。如果它不能跟踪后者,它可能仍然会返回它最多多长时间。无论哪种方式,必须使用有关自身的内部知识。

  

是否有限制和案例无法运作? ("提示"听起来有点可疑)。

显然,不实施return self._stop - self._current __length_hint__的对象无法正常工作。从根本上说,任何对其状态没有足够了解的对象都不能实现它。

链式生成器通常不实现它。例如,__len__不会转发(a ** 2 for a in range(5))的长度提示。如果您认为可能存在任意迭代器链,这是明智的:range只是预分配空间的优化,而获取要放入的内容可能更快进入那个空间。

在其他情况下,这可能是不可能的。无限和随机迭代器属于这一类,但也是外部资源的迭代器。

  

有没有办法获得拉链和发电机的提示?或者它只是迭代器的基础?

如果某个对象没有实现length_hint,那么没有。 Zip和发电机不是,可能出于上述效率原因。

另请注意,zip和generator对象是它们自己的迭代器。

__length_hint__

答案 3 :(得分:0)

  

有没有办法获得拉链和发电机的提示?或者它只是迭代器的基础?

在发电机的情况下,我不认为有一种简单或自动的方式,因为如果你给我一个任意的发电机,我不知道它是如何制造的,我怎么能确定是否它是有限的还是没有?我需要查看代码,如果使用其他函数,我需要查看这些函数以及如何调用它们......它会很快变得混乱,所以对于自动方式,所需的工作量要大得多而不是奖励

zip的情况下,我不知道它为什么没有它,看起来很容易检查每个元素的每个提示并返回它们中的最小值,也许它们不是添加它,因为你可以给它生成器,并且没有为什么要从它们获得提示?

所以在迭代器中它可能更好,因为它们是用iterator protocol

制作的
class MyIterator:
    def __iter__(self):
        return self
    def __next__(self):
        ...
        if condition_for_more_values:
            ...
            return next_value
        else:
            raise StopIteration

这样更容易在有意义时为__length_hint__函数添加逻辑,这就是为什么内置容器(list,tuple,str,set等)具有这样的功能,因为他们是这样的

class Container:
    ...

    def __len__(self):
        ...

    def iter(self):
        return Container_Iterator(self)

class Container_Iterator:

    def __init__(self,con):
        self.i=0
        self.data=con

    def __iter__(self):
        return self

    def __next__(self):
        if self.i<len(self.data):
            self.i+=1
            return self.data[self.i-1]
        else:
            raise StopIteration

因为Conatiner_Iterator可以访问它知道的每个容器的所有相关信息,所以它可以提供有意义的提示,它可以像

一样简单
    def __length_hint__(self):
        return len(self.data) - self.i