关于python:生成器输出的长度

Length of generator output

本问题已经有最佳答案,请猛点这里访问。

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

1
2
3
4
5
6
7
8
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是生成器。


没有一个是因为您通常无法做到这一点-如果您有一个惰性无限生成器怎么办?例如:

1
2
3
4
5
def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a

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

如果您确实需要知道项目的数量,那么无论如何都无法一次线性地遍历它们,因此只能使用其他数据结构,例如常规列表。


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

或者更好:

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

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

或者,如果您想计算生成器中特定的内容:

1
2
3
4
5
6
7
8
9
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)

因此,对于那些想了解该讨论摘要的人。使用以下方法计算长度为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

1
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))

("列表,秒",1.9684218849870376)

2:test_list_compr.py:8:0.867 KiB

1
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

1
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

1
2
3
4
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

1
gen = (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
2
def 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


尝试使用more_itertools软件包以获得简单的解决方案。例:

1
2
3
4
5
6
7
8
>>> import more_itertools

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

>>> 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
25
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

使用方法如下:

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
42
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]


这是一个技巧,但是如果您真的想让len在常规可迭代项上工作(以某种方式使用),则可以创建自己的len版本。

len函数在本质上等效于以下内容(尽管实现通常会提供一些优化以避免不必要的查找):

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

因此,我们可以定义new_len进行尝试,如果__len__不存在,则可以通过消耗iterable来自己计算元素的数量:

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

上面的代码在Python 2/3中有效,并且(据我所知)应该涵盖所有可能的迭代类型。