刚刚从PEP 424(https://www.python.org/dev/peps/pep-0424/)遇到了这个非常棒的__length_hint__()
迭代器方法。哇!一种获取迭代器长度而不会耗尽迭代器的方法。
我的问题:
编辑:顺便说一句,我看到__length__hint__()
从当前位置计算到最后。即部分消耗的迭代器将报告剩余长度。有趣。
答案 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 backport或Py3.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