erz*_*zya 31 python yield generator python-internals yield-from
我的理解yield from是,它类似于yield从可迭代对象中获取每个项目。然而,我在以下示例中观察到不同的行为。
我有Class1
class Class1:
def __init__(self, gen):
self.gen = gen
def __iter__(self):
for el in self.gen:
yield el
Run Code Online (Sandbox Code Playgroud)
和 Class2 的不同之处仅在于yield将 for 循环替换为yield from
class Class2:
def __init__(self, gen):
self.gen = gen
def __iter__(self):
yield from self.gen
Run Code Online (Sandbox Code Playgroud)
下面的代码从给定类的实例中读取第一个元素,然后在 for 循环中读取其余元素:
a = Class1((i for i in range(3)))
print(next(iter(a)))
for el in iter(a):
print(el)
Run Code Online (Sandbox Code Playgroud)
Class1这会为和产生不同的输出Class2。对于Class1输出是
0
1
2
Run Code Online (Sandbox Code Playgroud)
Class2输出为
0
Run Code Online (Sandbox Code Playgroud)
yield from产生不同行为的背后机制是什么?
ti7*_*ti7 27
当您使用 时next(iter(instance_of_Class2)),当内部生成器(迭代器,而不是生成器!)超出范围(并被删除)时调用内部生成器,而使用 时iter(),仅关闭其实例.close()Class1iter()
>>> g = (i for i in range(3))
>>> b = Class2(g)
>>> i = iter(b) # hold iterator open
>>> next(i)
0
>>> next(i)
1
>>> del(i) # closes g
>>> next(iter(b))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)
PEP 342 中分两部分描述了此行为
.close()方法(Python 2.5 的新方法)
- 添加支持以确保在生成器迭代器被垃圾收集时调用 close()。
当多个生成器委托发生时,发生的情况会更清楚一些(也许令人惊讶);iter当其包装被删除时,只有被委托的生成器才会关闭
>>> g1 = (a for a in range(10))
>>> g2 = (a for a in range(10, 20))
>>> def test3():
... yield from g1
... yield from g2
...
>>> next(test3())
0
>>> next(test3())
10
>>> next(test3())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)
Class2有哪些选择可以让Class2行为更加符合您的期望?
值得注意的是,其他策略虽然没有视觉上令人愉悦的糖分,yield from也没有一些潜在的好处,但它们为您提供了一种与价值观互动的方式,这似乎是主要好处
>>> class Class3:
... def __init__(self, gen):
... self.iterator = iter(gen)
...
... def __iter__(self):
... return self.iterator
...
>>> c = Class3((i for i in range(3)))
>>> next(iter(c))
0
>>> next(iter(c))
1
Run Code Online (Sandbox Code Playgroud)
iter()不一致之处 - 请参阅下面的评论(即为什么不e关闭?)itertools.chain.from_iterable
>>> class Class5(collections.abc.Generator):
... def __init__(self, gen):
... self.gen = gen
... def send(self, value):
... return next(self.gen)
... def throw(self, value):
... raise StopIteration
... def close(self): # optional, but more complete
... self.gen.close()
...
>>> e = Class5((i for i in range(10)))
>>> next(e) # NOTE iter is not necessary!
0
>>> next(e)
1
>>> next(iter(e)) # but still works
2
>>> next(iter(e)) # doesn't close e?? (should it?)
3
>>> e.close()
>>> next(e)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.9/_collections_abc.py", line 330, in __next__
return self.send(None)
File "<stdin>", line 5, in send
StopIteration
Run Code Online (Sandbox Code Playgroud)
更好的线索是,如果您直接重试,next(iter(instance))则会引发StopIteration,表明生成器永久关闭(通过耗尽或.close()),以及为什么用循环对其进行迭代for不会产生更多值
>>> a = Class1((i for i in range(3)))
>>> next(iter(a))
0
>>> next(iter(a))
1
>>> b = Class2((i for i in range(3)))
>>> next(iter(b))
0
>>> next(iter(b))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)
但是,如果我们命名迭代器,它就会按预期工作
>>> b = Class2((i for i in range(3)))
>>> i = iter(b)
>>> next(i)
0
>>> next(i)
1
>>> j = iter(b)
>>> next(j)
2
>>> next(i)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)
对我来说,这表明当迭代器没有名称时,它会.close()在超出范围时调用
>>> def gen_test(iterable):
... yield from iterable
...
>>> g = gen_test((i for i in range(3)))
>>> next(iter(g))
0
>>> g.close()
>>> next(iter(g))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)
拆解结果,发现内部有些不一样
>>> a = Class1((i for i in range(3)))
>>> dis.dis(a.__iter__)
6 0 LOAD_FAST 0 (self)
2 LOAD_ATTR 0 (gen)
4 GET_ITER
>> 6 FOR_ITER 10 (to 18)
8 STORE_FAST 1 (el)
7 10 LOAD_FAST 1 (el)
12 YIELD_VALUE
14 POP_TOP
16 JUMP_ABSOLUTE 6
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE
>>> b = Class2((i for i in range(3)))
>>> dis.dis(b.__iter__)
6 0 LOAD_FAST 0 (self)
2 LOAD_ATTR 0 (gen)
4 GET_YIELD_FROM_ITER
6 LOAD_CONST 0 (None)
8
10 POP_TOP
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
值得注意的是,该yield from版本有GET_YIELD_FROM_ITER
如果
TOS是生成器迭代器或协程对象,则保持原样。否则,执行TOS = iter(TOS).
(巧妙地,YIELD_FROM关键字似乎在 3.11 中被删除)
因此,如果给定的可迭代对象(对于类)是生成器迭代器,它将直接传递,给出我们(可能)期望的结果
传递一个不是生成器的迭代器(iter()在两种情况下每次都会创建一个新的迭代器)
>>> a = Class1([i for i in range(3)])
>>> next(iter(a))
0
>>> next(iter(a))
0
>>> b = Class2([i for i in range(3)])
>>> next(iter(b))
0
>>> next(iter(b))
0
Run Code Online (Sandbox Code Playgroud)
快速关闭Class1内部发电机
>>> g = (i for i in range(3))
>>> a = Class1(g)
>>> next(iter(a))
0
>>> next(iter(a))
1
>>> a.gen.close()
>>> next(iter(a))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)
iter如果实例被弹出,生成器仅在删除时关闭
>>> g = (i for i in range(10))
>>> b = Class2(g)
>>> i = iter(b)
>>> next(i)
0
>>> j = iter(b)
>>> del(j) # next() not called on j
>>> next(i)
1
>>> j = iter(b)
>>> next(j)
2
>>> del(j) # generator closed
>>> next(i) # now fails, despite range(10) above
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)