Длина выходного сигнала генератора

Python предоставляет хороший метод для получения длины нетерпеливого, iterable, 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., кажется, я ошибся, пытаясь это сделать, он отлично работает. Извините за неприятности.

Ответ 1

Существует не один, потому что вы не можете сделать это в общем случае - что, если у вас есть ленивый бесконечный генератор? Например:

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

Это никогда не заканчивается, но будет генерировать числа Фибоначчи. Вы можете получить столько чисел Фибоначчи, сколько хотите, набрав next().

Если вам действительно нужно знать количество элементов, которые есть, вы не можете проходить через них линейно один раз в любом случае, поэтому просто используйте другую структуру данных, такую ​​как обычный список.

Ответ 2

Самый простой способ - это просто sum(1 for _ in gen), где gen - ваш генератор.

Ответ 3

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)

Ответ 4

Вы можете использовать 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)

Ответ 6

По определению, только некоторое подмножество генераторов вернется после некоторого количества аргументов (имеет заданную длину), и даже тогда только подмножество этих конечных генераторов имеет предсказуемый конец (доступ к генератору может иметь сторону -эффекты, которые могли бы остановить генератор раньше).

Если вы хотите внедрить методы длины для своего генератора, вы должны сначала определить, что вы считаете "длиной" (это общее количество элементов? количество оставшихся элементов?), а затем обернуть ваш генератор в классе, Вот пример:

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]

Ответ 7

Это взломать, но если вы действительно хотите, чтобы 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 и (насколько мне известно) должны охватывать все мыслимые типы итераций.

Ответ 8

Попробуйте more_itertools пакет для простого решения. Пример:

>>> import more_itertools

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

>>> more_itertools.ilen(it)
5

См. этот пост для другого применяемого примера.