python - 如何截取生成器的第一个值并透明地从其余值中产生

标签 python generator yield-from

更新:我已经开始了thread on python-ideas为此目的提出附加语法或 stdlib 函数(即指定 yield from 发送的第一个值)。到目前为止 0 回复... :/

如何截取子生成器的第一个产生值,但使用 yield from 将剩余的迭代委托(delegate)给后者?
例如,假设我们有一个任意的双向生成器 subgen , 我们想把它包装在另一个生成器中 gen . gen的目的|是截取subgen的第一个产生值并委派剩下的一代—— 包括发送的值、抛出的异常、.close() 等。 ——给子生成器。
首先想到的可能是这样的:

def gen():
    g = subgen()

    first = next(g)
    # do something with first...
    yield "intercepted"

    # delegate the rest
    yield from g
但这是错误的,因为当调用者 .send s 得到第一个值后返回给生成器,它将作为 yield "intercepted" 的值结束表达式,它被忽略,而是 g将收到None作为第一个 .send值,作为 yield from 语义的一部分.
所以我们可能会考虑这样做:
def gen():
    g = subgen()

    first = next(g)
    # do something with first...
    received = yield "intercepted"
    g.send(received)

    # delegate the rest
    yield from g
但是我们在这里所做的只是将问题向后退一步:只要我们调用 g.send(received) ,生成器继续执行,直到到达下一个 yield 语句才停止,它的值成为 .send 的返回值称呼。所以我们还必须拦截并重新发送它。然后发送那个,然后再发送一次,依此类推……所以这行不通。
基本上,我要的是 yield from通过一种方法来自定义发送到生成器的第一个值是什么:
def gen():
    g = subgen()

    first = next(g)
    # do something with first...
    received = yield "intercepted"

    # delegate the rest
    yield from g start with received  # pseudocode; not valid Python
...但不必重新实现 yield from 的所有语义我。也就是说,费力且维护性差的解决方案是:
def adaptor(generator, init_send_value=None):
    send = init_send_value
    try:
        while True:
            send = yield generator.send(send)
    except StopIteration as e:
        return e.value
这基本上是对 yield from 的错误重新实现(它缺少对 throwclose 等的处理)。理想情况下,我想要一些更优雅、更少冗余的东西。

最佳答案

如果您尝试使用 yield from 将此生成器包装器实现为生成器函数,那么您的问题基本上归结为是否可以指定发送到“产生于”生成器的第一个值。它不是。
如果您查看 yield from 的正式规范PEP 380 中的表达式,你可以明白为什么。该规范包含一段(令人惊讶的复杂)示例代码,其行为与 yield from 相同。表达。前几行是:

_i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    ...
可以看到,对迭代器做的第一件事就是调用 next()就可以了,基本相当于.send(None) .无法跳过该步骤,您的生成器将始终收到另一个 None每当yield from用来。
我想出的解决方案是使用类而不是生成器函数来实现生成器协议(protocol):
class Intercept:
    def __init__(self, generator):
        self._generator = generator
        self._intercepted = False

    def __next__(self):
        return self.send(None)

    def send(self, value):
        yielded_value = self._generator.send(value)

        # Intercept the first value yielded by the wrapped generator and 
        # replace it with a different value.
        if not self._intercepted:
            self._intercepted = True

            print(f'Intercepted value: {yielded_value}')

            yielded_value = 'intercepted'

        return yielded_value

    def throw(self, type, *args):
        return self._generator.throw(type, *args)

    def close(self):
        self._generator.close()
__next__() , send() , throw() , close()Python Reference Manual 中有描述.
该类包装了在创建时传递给它的生成器,它将模仿它的行为。它唯一改变的是生成器产生的第一个值在返回给调用者之前被另一个值替换。
我们可以使用示例生成器 f() 来测试行为。它产生两个值和一个函数main()它将值发送到生成器,直到生成器终止:
def f():
    y = yield 'first'
    print(f'f(): {y}')

    y = yield 'second'
    print(f'f(): {y}')

def main():
    value_to_send = 0
    gen = f()

    try:
        x = gen.send(None)

        while True:
            print(f'main(): {x}')

            # Send incrementing integers to the generator.
            value_to_send += 1
            x = gen.send(value_to_send)
    except StopIteration:
        print('main(): StopIteration')    
      
main()
运行时,此示例将产生以下输出,显示哪些值到达生成器以及哪些值由生成器返回:
main(): first
f(): 1
main(): second
f(): 2
main(): StopIteration
包装发电机f()通过更改语句 gen = f()gen = Intercept(f()) ,产生以下输出,表明第一个产生的值已被替换:
Intercepted value: first
main(): intercepted
f(): 1
main(): second
f(): 2
由于对任何生成器 API 的所有其他调用都直接转发到包装生成器,因此它的行为应该与包装生成器本身等效。

关于python - 如何截取生成器的第一个值并透明地从其余值中产生,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65369447/

相关文章:

javascript - JavaScript for of 循环中迭代器和生成器的区别

python - Python 2 中的“yield from”替代品

python - TensorFlow GPU CUDA CUDNN 错误

javascript - 在 JavaScript 中,生成器函数中的 `return someValue` 是反模式吗?

python - 尝试除了不必要的步骤

python - 循环中的发电机组被覆盖

python 在函数中使用yield from

python - 使用产量生成器从列表中删除连续的重复项?

python - 如何获取带有空行的多行输入 - Python

python - py2小程序 : command not found