【问题标题】:Python 3.x: Test if generator has elements remainingPython 3.x:测试生成器是否有剩余元素
【发布时间】:2012-01-25 18:37:00
【问题描述】:

当我在 for 循环中使用生成器时,它似乎“知道”,当没有更多元素产生时。现在,我必须使用没有 for 循环的生成器,并手动使用 next() 来获取下一个元素。我的问题是,我怎么知道是否没有更多元素?

我只知道:next() 引发异常(StopIteration),如果什么都没有,那么对于这样一个简单的问题,BUT 是不是有点太“重”了?难道没有has_next()之类的方法吗?

以下几行应该清楚我的意思:

#!/usr/bin/python3

# define a list of some objects
bar = ['abc', 123, None, True, 456.789]

# our primitive generator
def foo(bar):
    for b in bar:
        yield b

# iterate, using the generator above
print('--- TEST A (for loop) ---')
for baz in foo(bar):
    print(baz)
print()

# assign a new iterator to a variable
foobar = foo(bar)

print('--- TEST B (try-except) ---')
while True:
    try:
        print(foobar.__next__())
    except StopIteration:
        break
print()

# assign a new iterator to a variable
foobar = foo(bar)

# display generator members
print('--- GENERATOR MEMBERS ---')
print(', '.join(dir(foobar)))

输出如下:

--- TEST A (for loop) ---
abc
123
None
True
456.789

--- TEST B (try-except) ---
abc
123
None
True
456.789

--- GENERATOR MEMBERS ---
__class__, __delattr__, __doc__, __eq__, __format__, __ge__, __getattribute__, __gt__, __hash__, __init__, __iter__, __le__, __lt__, __name__, __ne__, __new__, __next__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, close, gi_code, gi_frame, gi_running, send, throw

感谢大家,祝您有美好的一天! :)

【问题讨论】:

    标签: python-3.x iteration generator yield next


    【解决方案1】:

    这可能无法准确回答您的问题,但我在这里找到了一种方法,可以优雅地从生成器中获取结果,而无需编写 try: 块。谷歌搜索后我发现了这一点:

    def g():
        yield 5
    
    result = next(g(), None)
    

    现在result5None,这取决于您在迭代器上调用next 的次数,或者取决于生成器函数是否提前返回而不是产生。

    我非常喜欢将None 作为输出而不是“正常”条件下的提升,因此在这里避开 try/catch 是一个巨大的胜利。如果情况需要,还有一个简单的地方可以添加 None 以外的默认值。

    【讨论】:

      【解决方案2】:

      在一般情况下,不可能事先知道迭代器的结束,因为可能必须运行任意代码来决定结束。缓冲元素可以帮助以成本揭示事物 - 但这很少有用。

      在实践中,当人们现在只想从迭代器中获取一个或几个元素,但不想编写丑陋的异常处理代码(如问题中所示)时,就会出现问题。事实上,将“StopIteration”这个概念放入普通的应用程序代码中是非 Python 的。而且在 python 级别上的异常处理相当耗时——尤其是当它只需要一个元素时。

      最好的处理这些情况的pythonic方法是使用for .. break [.. else],例如:

      for x in iterator:
          do_something(x)
          break
      else:
          it_was_exhausted()
      

      或者使用默认的内置next()函数

      x = next(iterator, default_value)
      

      或使用迭代器助手,例如来自itertools 模块,用于重新布线,例如:

      max_3_elements = list(itertools.islice(iterator, 3))
      

      然而,一些迭代器公开了“长度提示”(PEP424):

      >>> gen = iter(range(3))
      >>> gen.__length_hint__()
      3
      >>> next(gen)
      0
      >>> gen.__length_hint__()
      2
      

      注意:普通应用代码不应使用iterator.__next__()。这就是他们在 Python2 中将其从 iterator.next() 重命名的原因。并且在没有默认值的情况下使用next() 也好不到哪里去......

      【讨论】:

        【解决方案3】:

        这是一个很好的问题。我将尝试向您展示我们如何使用 Python 的内省能力和开源来获得答案。我们可以使用dis 模块窥探幕后​​,看看 CPython 解释器如何在迭代器上实现 for 循环。

        >>> def for_loop(iterable):
        ...     for item in iterable:
        ...         pass  # do nothing
        ...     
        >>> import dis
        >>> dis.dis(for_loop)
          2           0 SETUP_LOOP              14 (to 17) 
                      3 LOAD_FAST                0 (iterable) 
                      6 GET_ITER             
                >>    7 FOR_ITER                 6 (to 16) 
                     10 STORE_FAST               1 (item) 
        
          3          13 JUMP_ABSOLUTE            7 
                >>   16 POP_BLOCK            
                >>   17 LOAD_CONST               0 (None) 
                     20 RETURN_VALUE         
        

        多汁的位似乎是 FOR_ITER 操作码。我们无法使用dis 进行更深入的研究,所以让我们在 CPython 解释器的源代码中查找 FOR_ITER。如果您四处寻找,您会在Python/ceval.c 中找到它;你可以查看它here。以下是全部内容:

            TARGET(FOR_ITER)
                /* before: [iter]; after: [iter, iter()] *or* [] */
                v = TOP();
                x = (*v->ob_type->tp_iternext)(v);
                if (x != NULL) {
                    PUSH(x);
                    PREDICT(STORE_FAST);
                    PREDICT(UNPACK_SEQUENCE);
                    DISPATCH();
                }
                if (PyErr_Occurred()) {
                    if (!PyErr_ExceptionMatches(
                                    PyExc_StopIteration))
                        break;
                    PyErr_Clear();
                }
                /* iterator ended normally */
                x = v = POP();
                Py_DECREF(v);
                JUMPBY(oparg);
                DISPATCH();
        

        你知道这是如何工作的吗?我们尝试从迭代器中抓取一个项目;如果我们失败了,我们检查引发了什么异常。如果是StopIteration,我们清除它并认为迭代器已耗尽。

        那么 for 循环如何“知道”迭代器何时耗尽?答案:它没有——它必须尝试抓取一个元素。但为什么呢?

        部分答案是简单。实现迭代器的部分好处在于您只需要定义一个操作:抓取下一个元素。但更重要的是,它使迭代器惰性:它们只会产生绝对必须的值。

        最后,如果您真的缺少此功能,那么您自己实现它是微不足道的。这是一个例子:

        class LookaheadIterator:
        
            def __init__(self, iterable):
                self.iterator = iter(iterable)
                self.buffer = []
        
            def __iter__(self):
                return self
        
            def __next__(self):
                if self.buffer:
                    return self.buffer.pop()
                else:
                    return next(self.iterator)
        
            def has_next(self):
                if self.buffer:
                    return True
        
                try:
                    self.buffer = [next(self.iterator)]
                except StopIteration:
                    return False
                else:
                    return True
        
        
        x  = LookaheadIterator(range(2))
        
        print(x.has_next())
        print(next(x))
        print(x.has_next())
        print(next(x))
        print(x.has_next())
        print(next(x))
        

        【讨论】:

          【解决方案4】:

          您编写的两个语句以完全相同的方式处理查找生成器的末尾。 for 循环只是调用 .next() 直到引发 StopIteration 异常然后终止。

          http://docs.python.org/tutorial/classes.html#iterators

          因此,我不认为等待 StopIteration 异常是处理问题的“繁重”方式,而是生成器的设计使用方式。

          【讨论】:

            猜你喜欢
            • 2022-01-16
            • 1970-01-01
            • 1970-01-01
            • 2017-10-04
            • 2017-01-18
            • 2021-04-01
            • 2016-10-29
            • 2015-09-14
            • 1970-01-01
            相关资源
            最近更新 更多