如何 len (发生器())

Python 生成器 非常有用。与返回列表的函数相比,它们具有优势。然而,你可以 len(list_returning_function())。有去 len(generator_function())的路吗?

更新:
len(list(generator_function()))当然可以... ..。
我正在尝试使用我在一个新的发电机里创建的一个发电机。作为新发电机计算的一部分,它需要知道旧发电机的长度。然而,我希望将它们与生成器的相同属性保持在一起,特别是——不要在内存中维护整个列表,因为它可能是 非常长的。

更新2:
假设生成器 知道的目标长度甚至从第一步。另外,没有理由维护 len()语法。例如,如果 Python 中的函数是对象,我是否可以将长度赋给这个对象的一个变量,这个变量可以被新的生成器访问?

149141 次浏览

您可以使用 len(list(generator)),但是如果您真的打算放弃这些结果,那么您可以使某些东西更有效率。

你可以使用 len(list(generator_function())。然而,这会消耗生成器,但这是您可以查明生成了多少元素的唯一方法。因此,如果您还想使用这些项目,可能需要将列表保存在某个地方。

a = list(generator_function())
print(len(a))
print(a[0])

生成器没有长度,它们毕竟不是集合。

生成器是 具有内部状态的功能(和花哨的语法)。您可以重复调用它们以获得一个值序列,因此可以在循环中使用它们。但是它们不包含任何元素,所以要求生成器的长度就像要求函数的长度一样。

如果 Python 中的函数是对象,我是否可以将长度赋给 这个对象的变量是否可以被新的生成器访问?

函数是对象,但不能为它们分配新属性。原因可能是为了尽可能地保持这样一个基本对象的效率。

然而,您可以简单地从函数返回 (generator, length)对,或者将生成器包装在一个简单的对象中,如下所示:

class GeneratorLen(object):
def __init__(self, gen, length):
self.gen = gen
self.length = length


def __len__(self):
return self.length


def __iter__(self):
return self.gen


g = some_generator()
h = GeneratorLen(g, 1)
print len(h), list(h)

假设我们有一个发电机:

def gen():
for i in range(10):
yield i

我们可以把生成器,连同已知的长度,封装在一个对象中:

import itertools
class LenGen(object):
def __init__(self,gen,length):
self.gen=gen
self.length=length
def __call__(self):
return itertools.islice(self.gen(),self.length)
def __len__(self):
return self.length


lgen=LenGen(gen,10)

LenGen的实例本身就是生成器,因为调用它们会返回一个迭代器。

现在我们可以使用 lgen发生器代替 gen,也可以访问 len(lgen):

def new_gen():
for i in lgen():
yield float(i)/len(lgen)


for i in new_gen():
print(i)

在其他答案中建议的到 list的转换是最好的方法,如果您事后仍然希望处理生成器元素,但有一个缺陷: 它使用 O (n)内存。您可以在不使用那么多内存的情况下计算生成器中的元素:

sum(1 for x in generator)

当然,请注意,在常见的 Python 实现中,这可能比 len(list(generator))慢,如果生成器的长度足以影响内存的复杂性,那么操作将需要相当长的时间。尽管如此,我个人还是更喜欢这个解决方案,因为它描述了我想要得到的东西,而且它不会给我任何不需要的额外信息(比如所有元素的列表)。

还要听听德尔南的建议: 如果您丢弃了生成器的输出,那么很可能有一种方法可以在不运行生成器的情况下计算元素的数量,或者以另一种方式计算元素的数量。

通过创建您自己的可迭代对象,您可以将生成器的好处与 len()的确定性结合起来:

class MyIterable(object):
def __init__(self, n):
self.n = n


def __len__(self):
return self.n


def __iter__(self):
self._gen = self._generator()
return self


def _generator(self):
# Put your generator code here
i = 0
while i < self.n:
yield i
i += 1


def next(self):
return next(self._gen)


mi = MyIterable(100)
print len(mi)
for i in mi:
print i,

这基本上是 xrange的一个简单实现,它返回一个可以获取 len 的对象,但是不创建显式列表。

你可以使用 reduce

对于 Python 3:

>>> import functools
>>> def gen():
...     yield 1
...     yield 2
...     yield 3
...
>>> functools.reduce(lambda x,y: x + 1, gen(), 0)

在 Python2中,reduce位于全局名称空间中,因此导入是不必要的。

你可以使用 send作为黑客:

def counter():
length = 10
i = 0
while i < length:
val = (yield i)
if val == 'length':
yield length
i += 1


it = counter()
print(it.next())
#0
print(it.next())
#1
print(it.send('length'))
#10
print(it.next())
#2
print(it.next())
#3