Python 3.x:测试生成器是否剩余元素

mad*_*ada 17 iteration yield generator next python-3.x

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

我只知道:next()引发一个异常(StopIteration),如果没有剩下的东西,那么对于这样一个简单的问题来说,这不是一个例外吗?是不是像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)))
Run Code Online (Sandbox Code Playgroud)

输出如下:

--- 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
Run Code Online (Sandbox Code Playgroud)

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

Ori*_*Ori 23

这是一个很好的问题.我将尝试向您展示如何使用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         
Run Code Online (Sandbox Code Playgroud)

多汁的位似乎是FOR_ITER操作码.我们不能深入使用dis,所以让我们在CPython解释器的源代码中查找FOR_ITER.如果你四处寻找,你会发现它Python/ceval.c; 你可以在这里查看.这是整个事情:

    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();
Run Code Online (Sandbox Code Playgroud)

你看到这是如何工作的吗?我们试图从迭代器中获取一个项目; 如果我们失败了,我们检查引发了什么异常.如果是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))
Run Code Online (Sandbox Code Playgroud)

  • 我刚刚意识到我想要做 `dis` 来学习 `numpy`。;) (2认同)

kxr*_*kxr 8

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

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

处理这些情况的最佳 Python 方法是使用for .. break [.. else]

for x in iterator:
    do_something(x)
    break
else:
    it_was_exhausted()
Run Code Online (Sandbox Code Playgroud)

或使用默认的内置next()函数,例如

x = next(iterator, default_value)
Run Code Online (Sandbox Code Playgroud)

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

max_3_elements = list(itertools.islice(iterator, 3))
Run Code Online (Sandbox Code Playgroud)

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

>>> gen = iter(range(3))
>>> gen.__length_hint__()
3
>>> next(gen)
0
>>> gen.__length_hint__()
2
Run Code Online (Sandbox Code Playgroud)

注意:iterator.__next__() 普通应用程序代码不应使用。这就是他们iterator.next()在 Python2 中将其重命名的原因。而且next()不使用默认值也好不了多少......


Chr*_*ris 7

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

def g():
    yield 5

result = next(g(), None)
Run Code Online (Sandbox Code Playgroud)

Nowresult是 或5None具体取决于您在迭代器上调用 next 的次数,或者取决于生成器函数是否提前返回而不是屈服。

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


MDT*_*MDT 6

您编写的两个陈述涉及以完全相同的方式查找生成器的结尾.for循环只调用.next(),直到引发StopIteration异常,然后终止.

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

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