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)
在一般情况下不可能事先知道迭代器的结束,因为可能必须运行任意代码来决定结束。缓冲元素可以帮助以代价揭示事物——但这很少有用。
在实践中,当人们现在只想从迭代器中获取一个或几个元素,但又不想编写丑陋的异常处理代码(如问题中所示)时,就会出现问题。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()不使用默认值也好不了多少......
这可能无法准确回答您的问题,但我在这里找到了一种方法,希望可以优雅地从生成器中获取结果,而无需编写块try:。后来谷歌搜索了一下我发现了这一点:
def g():
yield 5
result = next(g(), None)
Run Code Online (Sandbox Code Playgroud)
Nowresult是 或5,None具体取决于您在迭代器上调用 next 的次数,或者取决于生成器函数是否提前返回而不是屈服。
我非常喜欢将其None作为输出处理,而不是在“正常”条件下加注,因此在这里避开 try/catch 是一个巨大的胜利。如果情况需要,还有一个简单的地方可以添加除 之外的默认值None。
您编写的两个陈述涉及以完全相同的方式查找生成器的结尾.for循环只调用.next(),直到引发StopIteration异常,然后终止.
http://docs.python.org/tutorial/classes.html#iterators
因此,我不认为等待StopIteration异常是处理问题的"重要"方式,而是设计使用的生成器的方式.
| 归档时间: |
|
| 查看次数: |
9169 次 |
| 最近记录: |