本问题已经有最佳答案,请猛点这里访问。
Python提供了一种很好的方法来获取渴望的可迭代len(x)的长度。 但是对于以生成器理解和函数表示的惰性可迭代对象,我找不到类似的东西。 当然,写这样的东西并不难:
1
2
3
4
5
6
7
8def 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.,似乎我在尝试时出错,它可以正常工作。 抱歉,添麻烦了。
建议仅将标题更改为发电机输出长度-可以扔掉重复的物品。 否则,这个问题就会与另一个混淆。
reimplementing a bicycle-几乎就像在重新发明轮子一样,只有程序员这样说。
最简单的方法可能只是sum(1 for _ in gen),其中gen是生成器。
尽管我喜欢此解决方案,但这里的主要缺点是,通过阅读您想要实现的代码,它一点都不明显。如果我在其他人的代码中看到这一行,我会停下来想一想"他为什么要在这里取这笔钱?" -除非之前没有看到这个" hack"。
@CharlesSalvia那是对恕我直言的评论。 Id说,获取生成器的长度值得评论。
另一个主要缺点是,它耗尽了发电机的功率只是为了达到长度,这通常首先破坏了发电机的整个目的。
请注意,这可能会减少内存消耗,但似乎比仅将其转换为列表要慢。
没有一个是因为您通常无法做到这一点-如果您有一个惰性无限生成器怎么办?例如:
1
2
3
4
5def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
这永远不会终止,但会生成斐波那契数。您可以通过调用next()获得任意数量的斐波纳契数。
如果您确实需要知道项目的数量,那么无论如何都无法一次线性地遍历它们,因此只能使用其他数据结构,例如常规列表。
我不确定我是否相信/接受这种解释。 sum采取可迭代方式,即使该可迭代方式可能是无限的,因此"在一般情况下您不能做到"比在一般情况下可以做到的要多。也许更可能的原因是人们"期望" len为O(1),这对于一般的可迭代项而言不是吗?
常规列表会占用更多内存,这是OP希望避免的事情。
@Steve Jessop:如果您有很多对象,则将它们计数通常显然是O(n)。如果在收集对象时跟踪对象的数量,则为O(1)。在许多特殊情况下,您可能可以使用对象的性质来构成更好的算法(即通过称量米数)。如果对象在内存中对齐,则可以使用内存消耗来计数对象。但是对于发电机,通常没有这种方法。
我有一个过滤后的列表,预计大约为2000000000个元素。我不能只使用常规列表;我需要使用发电机。现在,由于这些元素的来源方式,我实际上可以非常高效地运行它们-我无法存储它们,因为我没有40个内存。这个答案对我完全没有用。
1
2def count(iter):
return sum(1 for _ in iter)
或者更好:
1
2
3
4
5def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
如果不是可迭代的,它将抛出一个TypeError。
或者,如果您想计算生成器中特定的内容:
1
2
3
4
5
6
7
8
9def 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)
因此,对于那些想了解该讨论摘要的人。使用以下方法计算长度为5000万的生成器表达式的最终最高分:
len(list(gen))
len([_ for _ in gen]),
sum(1 for _ in gen),
ilen(gen)(来自more_itertool),
reduce(lambda c, i: c + 1, gen, 0),
按执行性能(包括内存消耗)排序,会让您感到惊讶:
```
1:test_list.py:8:0.492 KiB
1gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
("列表,秒",1.9684218849870376)
2:test_list_compr.py:8:0.867 KiB
1gen = (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
1gen = (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
1
2
3
4d = 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
1gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('reduce,sec',13.436614598002052)
```
因此,len(list(gen))是最频繁且消耗较少的内存
这是最好的答案
您可以使用enumerate()遍历生成的数据流,然后返回最后一个数字-项目数。
我尝试将itertools.count()与itertools.izip()一起使用,但没有运气。这是我提出的最好/最短的答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#!/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的解决方案更好:
1
2def count_iterable(i):
return sum(1 for e in i)
使用reduce(function,iterable [,initializer])获得内存有效的纯函数解决方案:
1
2
3>>> iter ="This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
您的时间安排不对,因为迭代器正在被消耗。实际上,只有在len(list(iter))上的第一次尝试才对任何值进行迭代,所有其他值都在计数零长度序列。在我的测试中,reduce比len(list()),enumerate和sum慢。
@Blckknght谢谢,纠正了。
尝试使用more_itertools软件包以获得简单的解决方案。例:
1
2
3
4
5
6
7
8>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
>>> more_itertools.ilen(it)
5
有关另一个应用示例,请参见本文。
根据定义,只有一定数量的参数(具有预定义的长度)后,才会生成器的子集返回,即使这样,这些有限生成器中只有一个子集具有可预测的结局(访问生成器会产生副作用,即可以更早停止发电机)。
如果要为生成器实现length方法,则必须首先定义您认为的"长度"(是元素的总数还是剩余元素的数量?),然后将生成器包装在一个类中。这是一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class 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
使用方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42In [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)
in ()
----> 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]
这是实现可以为len()函数提供长度的迭代器/生成器的解决方案。您可以通过实现自己的__iter__方法以及必要时自己的__init__和__len__方法来从此类派生生成器。这种模式可能很有用,例如对于某些ORM类型的对象,您可以在其中执行SQL查询,然后使用游标(通过迭代器)逐行获取结果,并且__len__方法从实际的SQL查询中获取计数。
这是一个技巧,但是如果您真的想让len在常规可迭代项上工作(以某种方式使用),则可以创建自己的len版本。
len函数在本质上等效于以下内容(尽管实现通常会提供一些优化以避免不必要的查找):
1
2def len(iterable):
return iterable.__len__()
因此,我们可以定义new_len进行尝试,如果__len__不存在,则可以通过消耗iterable来自己计算元素的数量:
1
2
3
4
5def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)
上面的代码在Python 2/3中有效,并且(据我所知)应该涵盖所有可能的迭代类型。
覆盖内置函数将掩盖原始行为,从而导致难以(或不可能)调试代码。您确实应该为该函数使用一个不同的名称,即不要命名为len ...