Как посчитать предметы в генераторе, потребленные другим кодом - PullRequest
9 голосов
/ 10 июня 2011

Я создаю генератор, который потребляется другой функцией, но я все еще хотел бы знать, сколько элементов было сгенерировано:

lines = (line.rstrip('\n') for line in sys.stdin)
process(lines)
print("Processed {} lines.".format( ? ))

Лучшее, что я могу придумать, - это обернуть генератор классом, который ведет счет, или, может быть, вывернуть его наизнанку и отправить () вещи. Существует ли элегантный и эффективный способ узнать, сколько предметов у генератора производится, когда вы не тот, кто потребляет его в Python 2?

Редактировать: Вот что я закончил:

class Count(Iterable):
    """Wrap an iterable (typically a generator) and provide a ``count``
    field counting the number of items.

    Accessing the ``count`` field before iteration is finished will
    invalidate the count.
    """
    def __init__(self, iterable):
        self._iterable = iterable
        self._counter = itertools.count()

    def __iter__(self):
        return itertools.imap(operator.itemgetter(0), itertools.izip(self._iterable, self._counter))

    @property
    def count(self):
        self._counter = itertools.repeat(self._counter.next())
        return self._counter.next()

Ответы [ 6 ]

13 голосов
/ 15 мая 2012

Если вам все равно, что вы потребляете генератор, вы можете просто сделать:

sum(1 for x in gen)
10 голосов
/ 10 июня 2011

Вот еще один способ, используя itertools.count() пример:

import itertools

def generator():
    for i in range(10):
       yield i

def process(l):
    for i in l:
        if i == 5:
            break

def counter_value(counter):
    import re
    return int(re.search('\d+', repr(counter)).group(0))

counter = itertools.count()
process(i for i, v in itertools.izip(generator(), counter))

print "Element consumed by process is : %d " % counter_value(counter)
# output: Element consumed by process is : 6

Надеюсь, это было полезно.

8 голосов
/ 10 июня 2011

Обычно я просто превращаю генератор в список и принимаю его длину. Если у вас есть основания полагать, что это будет занимать слишком много памяти, вам лучше всего подойти к предложенному вами классу-оболочке. Это не так уж плохо:

class CountingIterator(object):
    def __init__(self, it):
        self.it = it
        self.count = 0
    def __iter__(self):
        return self
    def next(self):
        nxt = next(self.it)
        self.count += 1
        return nxt
    __next__ = next

(последняя строка для прямой совместимости с Python 3.x.)

2 голосов
/ 10 июня 2011

Вот другой подход. Использование списка для вывода подсчета немного уродливо, но довольно компактно:

def counter(seq, count_output_list):
    for x in seq:
        count_output_list[0] += 1
        yield x

Используется так:

count = [0]
process(counter(lines, count))
print count[0]

В качестве альтернативы можно counter() принять диктовку, в которой он может добавить ключ "count" или объект, для которого он может установить count член.

1 голос
/ 31 мая 2017

Если вам не нужно возвращать счетчик и просто хотите записать его, вы можете использовать блок finally:

def generator():
    i = 0
    try:
        for x in range(10):
            i += 1
            yield x
    finally:
        print '{} iterations'.format(i)

[ n for n in generator() ]

Который производит:

10 iterations
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 голос
/ 24 июня 2015

Это еще одно решение, подобное @ sven-marnach:

class IterCounter(object):
  def __init__(self, it):
    self._iter = it
    self.count = 0

  def _counterWrapper(self, it):
    for i in it:
      yield i
      self.count += 1

  def __iter__(self):
    return self._counterWrapper(self._iter)

Я обернул итератор функцией генератора и избегал переопределения next.Результат является итеративным (не итератором, потому что в нем отсутствует метод next), но если он достаточен, он быстрее.В моих тестах это на 10% быстрее.

...