__length_hint__()
。哇!这是一种在不耗尽迭代器的情况下获取迭代器长度的方法。我的问题如下:
- 有没有简单的解释说明这个神奇的方法是如何工作的?我只是好奇。
- 它是否有限制或者某些情况下无法使用?("hint"听起来有点可疑)
- 是否有办法对zip和生成器也获得提示?还是只有迭代器才有这个基本功能?
__length_hint__()
从当前位置计数到末尾。即部分消耗的迭代器将报告剩余长度。很有趣。__length_hint__()
。哇!这是一种在不耗尽迭代器的情况下获取迭代器长度的方法。__length_hint__()
从当前位置计数到末尾。即部分消耗的迭代器将报告剩余长度。很有趣。哇!一种可以获取迭代器长度而不用耗尽迭代器的方法。
不是这样的。 这只是一种获取关于长度可能性的模糊提示,并没有要求它必须准确无误。
这个神奇的机制如何工作呢?有简单的解释吗?
迭代器实现了一个__length_hint__
方法,使用特定于迭代器的信息猜测输出元素的数量。这个猜测可能相当不错,也可能非常糟糕。例如,列表迭代器知道自己在列表中的位置和列表的长度,因此可以报告剩余元素的数量。
有限制或无法工作的情况吗?
如果迭代器没有足够的信息来猜测何时会停止,则无法实现有用的__length_hint__
。例如,生成器就没有这个方法。无限迭代器也无法实现有用的__length_hint__
,因为无法信号无限长度。
是否有办法对zip和生成器获得提示?还是这只适用于迭代器?
zip
实例和生成器都是迭代器的一种。不过,zip
和生成器类型都没有提供__length_hint__
方法。
MyNetworkConnection()
对象的可迭代对象,并在内部需要创建和分配内存以在Cython/C代码中表示它们的数据结构。如果我们可以大致估计迭代器中的项目数量,我们可以一次性分配足够大的内存块,以容纳所有项目并最小化重新调整大小。__len__(),我们就知道确切的长度,并可用于内存分配。但通常我们实际上并不知道确切的长度,因此估计值通过为我们提供“大概数字”来帮助我们提高性能。
这对于纯Python代码也肯定很有用,例如,可能会有一个用户面向的操作完成时间估计?
对于问题2,由于它只是一个提示,所以您不能指望它是精确的。如果提示过低,您仍然必须考虑分配新的内存,如果提示过高,则必须清理内存。我个人不知道其他限制或潜在问题。
对于问题3,我认为它同样适用于生成器,因为生成器是一个迭代器。
>>> import collections
>>> def my_generator(): yield
>>> gen = my_generator()
>>> isinstance(gen, collections.Iterator)
True
对于这个问题有几个答案,但它们都稍微偏离了重点: __length_hint__
不是魔法。它是一种协议。如果一个对象没有实现该协议,那就是这样。
a + b
。 +
运算符依赖于a.__add__
和b.__radd__
来实际执行操作。 int
实现__add__
表示算术加法(1 + 2 == 3
),而list
实现__add__
表示内容连接([1] + [2] == [1, 2]
)。 这是因为__add__
只是一个协议,如果对象提供了它,那么它们必须遵守该协议。 __add__
的定义基本上只是“取另一个操作数并返回一个对象”。+
没有单独的、普遍的意义。 如果操作数没有提供__add__
或_radd__
,则Python将无能为力。
回到实际问题,这意味着什么?
有没有一个简单的解释来解释这个魔法是如何工作的?我只是好奇。
所有的魔法都列在PEP 424中,但基本上是这样的:尝试 len(obj)
,回退到 obj.__length_hint__
,使用默认值。这就是所有的魔法。
在实践中,一个对象必须根据自身的了解实现__length_hint__
。例如,以range backport或Py3.6 C Code的range_iterator
为例:
return self._stop - self._current
显然,没有实现有限制条件和不能工作的情况吗?(“提示”听起来有点可疑)。
__length_hint__
或__len__
的对象是无法工作的。基本上,任何没有足够了解其状态的对象无法实现它。(a ** 2 for a in range(5))
将不会从range
中转发长度提示。如果您考虑到可能存在任意数量的迭代器链,则这是明智的:length_hint
只是为了预先分配空间而进行的优化,直接获取要放入该空间的内容可能更快。foo = zip([1,2,3], [1,2,3])
id(foo) == id(iter(foo)) # returns True in py3.5
from sys import getsizeof
n = 25
class MyClass1:
def __iter__(self):
for i in range(n):
yield i
class MyClass2:
def __iter__(self):
for i in range(n):
yield i
def __len__(self):
print("__len__ in MyClass2")
return n
class MyClass3:
def __iter__(self):
for i in range(n):
yield i
def __length_hint__(self):
print("__length_hint__ in MyClass3")
return n + 2
class MyClass4:
def __iter__(self):
for i in range(n):
yield i
def __len__(self) -> int:
print("__len__ in MyClass4")
return n
def __length_hint__(self):
print("__length_hint__ in MyClass4")
return n + 2
obj1 = MyClass1()
obj2 = MyClass2()
obj3 = MyClass3()
obj4 = MyClass4()
lst1 = list(obj1)
lst2 = list(obj2)
lst3 = list(obj3)
lst4 = list(obj4)
print("obj1:", getsizeof(lst1))
print("obj2:", getsizeof(lst2))
print("obj3:", getsizeof(lst3))
print("obj4:", getsizeof(lst4))
输出:
__len__ in MyClass2
__length_hint__ in MyClass3
__len__ in MyClass4
obj1: 312
obj2: 264
obj3: 280
obj4: 264
n = 25
是有意使用的,因为在这个解释器(3.10.6)中,list
在添加第24个项目后会进行过度分配,因此我们可以清楚地看到差异。
这种提示方式在PEP 424规范部分中提到。
obj1
应该是三个对象中最大的,因为它进行了过度分配。 obj3
比 obj2
大16字节,这是64位机器上两个指针的大小。在 obj4
中,由于 __len__
返回一个非负的正确整数,Python甚至没有调用 __length_hint__
方法。
__length_hint__
将是什么大小来计算大小。
from sys import getsizeof
class Zip(zip):
def __init__(self, *iterables, strict=False) -> None:
self.ITs = iterables
def __length_hint__(self):
try:
return min(len(it) for it in self.ITs)
except TypeError:
return NotImplemented
z1 = list(Zip(range(1_000_000), range(1_000_000)))
z2 = list(zip(range(1_000_000), range(1_000_000)))
print(z1 == z2)
print(getsizeof(z1)) # 8000056
print(getsizeof(z2)) # 8448728
这会减慢速度吗?实际上并不会:
from timeit import timeit
def test_zip():
z = zip
return list(z(range(10_000_000), range(10_000_000)))
def test_Zip():
z = Zip
return list(z(range(10_000_000), range(10_000_000)))
print(timeit(test_zip, globals=globals(), number=2)) # 1.622484583999721
print(timeit(test_Zip, globals=globals(), number=2)) # 1.578525709999667
class MyIterator:
def __iter__(self):
return self
def __next__(self):
...
if condition_for_more_values:
...
return next_value
else:
raise StopIteration
因此,在这里添加__length_hint__
函数的逻辑更容易,而且内置容器(列表、元组、字符串、集合等)具有这样的功能,因为它们是按照这种方式制作的。
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