具有指定长度的迭代器/生成器
Iterables / generators with specified length
可迭代对象是那些实现 __iter__
函数的对象,其中 returns 一个迭代器对象,即提供函数 __iter__
和 __next__
并且行为正确的对象。通常可迭代对象的大小是事先不知道的,也不期望可迭代对象知道迭代将持续多长时间;但是,在某些情况下,知道可迭代对象的长度很有价值,例如,在创建数组时。例如list(x for x in range(1000000))
,创建一个初始的小数组,满了就复制,重复多次,如解释的那样。当然,在这个例子中它不是那么重要,但它说明了这一点。
对于那些事先知道长度的可迭代对象,是否有使用协议?也就是说,是否有扩展Sized and Iterable but not Collection or Reversible的协议?语言特性好像没有这个协议,有没有知名的第三方库有这样的协议?此讨论与生成器有何关系?
如果我现在理解了您的问题,那么您仍在尝试将两个不能完全以这种方式结合的概念结合起来。 generator
是 iterator
的子 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
字节并且一直保留到最后。
可迭代对象是那些实现 __iter__
函数的对象,其中 returns 一个迭代器对象,即提供函数 __iter__
和 __next__
并且行为正确的对象。通常可迭代对象的大小是事先不知道的,也不期望可迭代对象知道迭代将持续多长时间;但是,在某些情况下,知道可迭代对象的长度很有价值,例如,在创建数组时。例如list(x for x in range(1000000))
,创建一个初始的小数组,满了就复制,重复多次,如
对于那些事先知道长度的可迭代对象,是否有使用协议?也就是说,是否有扩展Sized and Iterable but not Collection or Reversible的协议?语言特性好像没有这个协议,有没有知名的第三方库有这样的协议?此讨论与生成器有何关系?
如果我现在理解了您的问题,那么您仍在尝试将两个不能完全以这种方式结合的概念结合起来。 generator
是 iterator
的子 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 aslist
) 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
字节并且一直保留到最后。