Python 提供了一个很好的方法来获取一个渴望迭代的长度,
len(x)
即。但是对于由生成器理解和函数表示的惰性迭代器,我找不到任何类似的东西。当然,写这样的东西并不难:
def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n
但我无法摆脱一种我正在重新实现自行车的感觉。
(当我输入该函数时,我想到了一个想法:也许真的没有这样的函数,因为它“破坏”了它的参数。不过对我来说这不是问题)。
P.S.:关于第一个答案 - 是的,像
len(list(x))
这样的东西也可以,但这会大大增加内存的使用。
P.P.S.:重新检查...无视 P.S.,似乎我在尝试时犯了一个错误,它工作正常。抱歉给您带来麻烦。
最简单的方法可能就是
sum(1 for _ in gen)
,其中gen是你的发电机。
对于那些想知道讨论摘要的人。使用以下方法计算 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))
是使用频率最高,消耗内存最少的
没有一个是因为在一般情况下你不能这样做——如果你有一个懒惰的无限生成器怎么办?例如:
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
这永远不会终止,但会生成斐波那契数列。您可以通过调用
next()
获得任意数量的斐波那契数列。
如果你真的需要知道项目的数量,那么无论如何你都不能线性地遍历它们一次,所以只需使用不同的数据结构,比如常规列表。
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)
您可以使用 enumerate() 循环遍历生成的数据流,然后返回最后一个数字——项目数。
我尝试将 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)
根据定义,只有一部分生成器会在一定数量的参数(具有预定义的长度)后返回,即便如此,这些有限生成器中也只有一部分具有可预测的结束(访问生成器可能会产生副作用这可以提前停止发电机)。
如果你想为你的生成器实现长度方法,你必须首先定义你认为的“长度”(它是元素的总数吗?剩余元素的数量?),然后将你的生成器包装在一个类中。这是一个例子:
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
more_itertools
包以获得简单的解决方案。例子:
>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
<str_iterator at 0x4ab3630>
>>> more_itertools.ilen(it)
5
有关另一个应用示例,请参阅这篇文章。
这是一个 hack,但如果您真的想让
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)
以上在 Python 2/3 中工作,并且(据我所知)应该涵盖所有可以想象的迭代类型。