【问题标题】:How to count the items in a generator consumed by other code如何计算其他代码消耗的生成器中的项目
【发布时间】:2011-06-10 16:22:42
【问题描述】:

我正在创建一个被另一个函数消耗的生成器,但我仍然想知道生成了多少项:

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

我能想到的最好的办法是用一个保持计数的类包装生成器,或者可能把它翻过来然后 send() 东西进去。有没有一种优雅而有效的方法来查看生成器有多少项目当你不是 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()

【问题讨论】:

  • 当你说你创建生成器时,你的意思是你使用yield定义一个函数还是你从一个理解或类似的地方创建一个生成器对象?

标签: python count generator


【解决方案1】:

如果你不关心你正在使用生成器,你可以这样做:

sum(1 for x in gen)

【讨论】:

    【解决方案2】:

    这是使用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
    

    希望这对您有所帮助。

    【讨论】:

    • 你推进计数器只是为了阅读它,所以如果你这样做两次它是错误的。 itertools.izip(generator(), counter) 也只是(翻转)enumerate
    • 你应该在读出计数器时删除- 1。您的示例实际上应该打印 6,因为 process() 使用元素 0, 1, 2, 3, 4, 5
    • @Jochen Ritzel:抱歉,我不明白你对doing it twice? 的评论,我不明白enumerate() 怎么能得到相同的结果,因为我的想法是阅读计数器之后的结果,对不起,但我不知道你将如何使用enumerate() 来做到这一点(干净),但如果你说zip() 在这种情况下与itertools.izip() 相同,我同意。
    • @Jochen Ritzel:我添加了另一种方法来获取计数器值而不推进它我一开始也不喜欢它,我不知道这是否解决了你看到的问题:)。
    • 这就是我一直在寻找的东西(好吧,无论如何,在你的 repr() hack 之前。;)。对于更多免费的 itertools 优点,您可以这样做 itertools.imap(operator.itemgetter(0), itertools.izip(iterable, counter))
    【解决方案3】:

    通常,我只是将生成器变成一个列表并获取它的长度。如果您有理由假设这会消耗太多内存,那么您最好的选择似乎确实是您自己建议的包装类。不过还不错:

    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。)

    【讨论】:

      【解决方案4】:

      这是另一种方法。使用列表进行计数输出有点难看,但它非常紧凑:

      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 成员的对象。

      【讨论】:

        【解决方案5】:

        如果你不需要返回计数而只想记录它,你可以使用 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]
        

        【讨论】:

          【解决方案6】:

          这是另一个类似于@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%。

          【讨论】:

            【解决方案7】:

            此解决方案使用 more_itertools 包中的 side_effect

            from typing import TypeVar, Tuple, Iterator, Callable, Iterable
            from itertools import count
            from more_itertools import side_effect, peekable
            
            T = TypeVar("T")
            def counter_wrap(iterable: Iterable[T]) -> \
                    Tuple[Iterator[T], Callable[[], int]]:
                """
                Returns a new iterator based on ``iterable``
                and a getter that when called returns the number of times
                the returned iterator was called up until that time
                """
                counter = peekable(count())
                def get_count() -> int:
                    return counter.peek()
                return (
                    side_effect(lambda e: next(counter), iterable),
                    get_count
                )
            

            它可以用作:

            >>> iterator, counter = counter_wrap((1, 2, 3, 4, 5, 6, "plast", "last"))
            >>> counter()
            0
            >>> counter()  # Calling this has no side effect (counter not incremented)
            0
            >>> next(iterator)
            1
            >>> next(iterator)
            2
            >>> next(iterator)
            3
            >>> counter()  # Updates when the iterator returns an element
            3
            >>> next(iterator)
            4
            >>> next(iterator)
            5
            >>> next(iterator)
            6
            >>> next(iterator)
            'plast'
            >>> counter()
            7
            >>> next(iterator)
            'last'
            >>> counter()
            8
            >>> next(iterator)
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
            StopIteration
            >>> counter()
            8
            

            【讨论】:

              猜你喜欢
              • 2020-06-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-12-18
              • 1970-01-01
              • 2012-07-15
              • 1970-01-01
              相关资源
              最近更新 更多