发电机输出长度

Python 提供了一个很好的方法来获取渴望迭代的 len(x)的长度,即。但是我找不到任何类似于由生成器理解和函数表示的惰性迭代的东西。当然,写下这样的东西并不难:

def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n

但我无法摆脱一种感觉,那就是我正在重新实现一辆自行车。

(当我在输入函数时,一个想法闪过我的脑海: 也许真的没有这样的函数,因为它“破坏”了它的参数。不过对我的案子来说不是问题)。

附注: 关于第一个答案——是的,类似 len(list(x))的东西也可以,但是这会大大增加内存的使用。

附注: 重新检查... 无视附注,似乎我在尝试时犯了一个错误,它工作正常。对不起,麻烦了。

119150 次浏览

没有一个是因为在一般情况下不能这样做-如果有一个惰性无限生成器会怎样?例如:

def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a

这永远不会终止,但会生成斐波那契数。通过调用 next(),您可以得到任意多的斐波那契数字。

如果您真的需要知道项的数量,那么您无论如何都不能一次线性地遍历它们,所以只需使用不同的数据结构,例如常规列表。

可以使用枚举()遍历生成的数据流,然后返回最后一个数字——项的数量。

我尝试将 itertools.count ()与 itertools.izip ()一起使用,但是没有用,这是我想到的最好/最短的答案:

#!/usr/bin/python


import itertools


def func():
for i in 'yummy beer':
yield i


def icount(ifunc):
size = -1 # for the case of an empty iterator
for size, _ in enumerate(ifunc()):
pass
return size + 1


print list(func())
print 'icount', icount(func)


# ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r']
# icount 10

卡米尔•基谢尔(Kamil Kisiel)的解决方案要好得多:

def count_iterable(i):
return sum(1 for e in i)

最简单的方法可能只是 sum(1 for _ in gen),其中 gen 是您的发电机。

def count(iter):
return sum(1 for _ in iter)

或者更好的是:

def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)

如果它不可迭代,它将抛出一个 TypeError

或者,如果你想在生成器中计算一些特定的东西:

def count(iter, key=None):
if key:
if callable(key):
return sum(bool(key(x)) for x in iter)
return sum(x == key for x in iter)
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)

根据定义,只有一个子集的生成器将返回在一定数量的参数(有一个预定义的长度) ,即使那样,只有一个子集的这些有限生成器有一个可预测的结束(访问生成器可能有副作用,可以停止生成器更早)。

如果你想为你的生成器实现长度方法,你必须首先定义你认为的“长度”(是元素的总数吗?剩余元素的数量?),然后将生成器包装在一个类中。这里有一个例子:

class MyFib(object):
"""
A class iterator that iterates through values of the
Fibonacci sequence, until, optionally, a maximum length is reached.
"""


def __init__(self, length):
self._length = length
self._i = 0


def __iter__(self):
a, b = 0, 1
while not self._length or self._i < self._length:
a, b = b, a + b
self._i += 1
yield a


def __len__(self):
"This method returns the total number of elements"
if self._length:
return self._length
else:
raise NotImplementedError("Infinite sequence has no length")
# or simply return None / 0 depending
# on implementation

以下是使用方法:

In [151]: mf = MyFib(20)


In [152]: len(mf)
Out[152]: 20


In [153]: l = [n for n in mf]


In [154]: len(l)
Out[154]: 20


In [155]: l
Out[155]:
[1,
1,
2,
...
6765]




In [156]: mf0 = MyFib(0)


In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)


/tmp/ipython_edit_TWcV1I.py in __len__(self)
22             return self._length
23         else:
---> 24             raise NotImplementedError
25             # or simply return None / 0 depending
26             # on implementation


NotImplementedError:


In [158]: g = iter(mf0)


In [159]: l0 = [g.next(), g.next(), g.next()]


In [160]: l0
Out[160]: [1, 1, 2]

使用 Reduce (function,iterable [ ,initializer ])作为一个内存高效的纯功能性解决方案:

>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30

这是一个技巧,但是如果您真的想让 len在一个通用迭代器上工作(以这种方式使用它) ,那么您可以创建自己的 len版本。

len函数基本上等价于以下内容(尽管实现通常会提供一些优化以避免额外的查找) :

def len(iterable):
return iterable.__len__()

因此,我们可以定义我们的 new_len来尝试这样做,如果 __len__不存在,通过使用迭代器来计算元素的数量:

def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)

以上工作在 Python2/3中,并且(据我所知)应该涵盖所有可能的迭代类型。

尝试使用 more_itertools包获得一个简单的解决方案。例如:

>>> import more_itertools


>>> it = iter("abcde")                                         # sample generator
>>> it
<str_iterator at 0x4ab3630>


>>> more_itertools.ilen(it)
5

有关另一个应用示例,请参见 这篇文章

对于那些想知道讨论摘要的人。计算一个5000万长度的生成器表达式的最终得分如下:

  • len(list(gen)),
  • len([_ for _ in gen]),
  • sum(1 for _ in gen),
  • ilen(gen)(由 More _ itertools开始)、,
  • reduce(lambda c, i: c + 1, gen, 0),

按执行性能(包括内存消耗)排序,会让你大吃一惊:

#1: test_list.py:8: 0.492 KiB
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('list, sec', 1.9684218849870376)


#2: test_list_compr.py:8: 0.867 KiB
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, sec', 2.5885991149989422)


#3: test_sum.py:8: 0.859 KiB
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('sum, sec', 3.441088170016883)


#4: more_itertools/more.py:413: 1.266 KiB
d = deque(enumerate(iterable, 1), maxlen=1)
   

test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
('ilen, sec', 9.812256851990242)


#5: test_reduce.py:8: 0.859 KiB
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('reduce, sec', 13.436614598002052)

因此,len(list(gen))是最常用的、内存消耗最少的