【问题标题】:Modifying yield from's return value修改 yield from 的返回值
【发布时间】:2016-08-03 14:54:31
【问题描述】:

假设我有这些解析器:

parsers = {
    ".foo": parse_foo,
    ".bar", parse_bar
}

parse_fooparse_bar 都是生成行的生成器。如果我想创建一个调度函数,我会这样做:

def parse(ext):
    yield from parsers[ext]()

yield from 语法让我可以轻松地在生成器上下传输信息。

有什么方法可以在修改产量结果的同时保持隧道效应?
在破坏隧道的同时这样做很容易:

def parse(ext):
    for result in parsers[ext]():
        # Add the extension to the result
        result.ext = ext
        yield result

但是这种方式我不能一直使用.send().throw() 到解析器。

我想到的唯一方法是做一些像try: ... except Exception: ... 这样丑陋的事情并传递异常,同时对.send() 做同样的事情。它丑陋、凌乱且容易出错。

【问题讨论】:

  • 我认为你最好的选择可能是实现一个passthrough_map,它在将sendthrow 传递给你正在映射的生成器时执行map 所做的事情。 IIRC,做到这一点很棘手,但您只需要正确处理一次,然后您可以在需要该功能时重复使用它。

标签: python python-3.x generator yield-from


【解决方案1】:

parse_fooparse_bar 添加扩展:

def parse_foo(ext):
    # Existing code
    ...
    # Add an extension to the item(s)
    item.ext = ext

def parse(ext):
    yield from parsers[ext](ext)

或者只是在每个函数中硬编码:

def parse_foo():
    # Existing code
    ...
    # Add an extension to the item(s)
    item.ext = ".foo"

【讨论】:

  • 这打破了sendthrow
  • 这不起作用并且确实破坏了sendthrow
  • @user2357112 这对sendthrow 有何影响?
  • @ZachGates:使用yield from,外部生成器上的sendthrow 会传递到您正在使用的生成器yield from-ing。将map 挡住会阻止它工作。
  • @user2357112:感谢您的解释;我相信我已经解决了。
【解决方案2】:

除了try ... yield ... except,还有另一种方法:实现一个新的生成器。使用这个类,您可以转换底层生成器的所有输入和输出:

identity = lambda x: x
class map_generator:
  def __init__(self, generator, outfn = identity,
      infn = identity, throwfn = identity):
    self.generator = generator
    self.outfn = outfn
    self.infn = infn
    self.throwfn = throwfn
    self.first = True
  def __iter__(self):
    return self
  def __next__(self):
    return self.send(None)
  def _transform(self, value):
    if self.first:
      self.first = False
      return value
    else:
      return self.infn(value)
  def send(self, value):
    return self.outfn(self.generator.send(self._transform(value)))
  def throw(self, value):
    return self.outfn(self.generator.throw(self.throwfn(value)))
  def next(self): # for python2 support
    return self.__next__()

用法:

def foo():
  for i in "123":
    print("sent to foo: ", (yield i))

def bar():
  dupe = lambda x:2*x
  tripe = lambda x:3*x
  yield from map_generator(foo(), dupe, tripe)

i = bar()
print("received from bar: ", i.send(None))
print("received from bar: ", i.send("B"))
print("received from bar: ", i.send("C"))

...

received from bar:  11
sent to foo:  BBB
received from bar:  22
sent to foo:  CCC
received from bar:  33

编辑:您可能希望从 collections.Iterator 继承,但在此用例中不是必需的。

【讨论】:

  • 非常感谢您花时间回答我。我发现了一个名为 cotoolz 的包,它显然就是这样做的,但它是用 C 语言实现的,因此执行速度更快,所以我会继续使用它。
【解决方案3】:

不幸的是没有内置的功能可以做到这一点。您可以使用类自己实现它,但一个名为 cotoolz 的包实现了一个 map() 函数,它正是这样做的。

他们的 map 函数比内置的 map() 慢 4 倍,但它支持生成器协议,并且比类似的 Python 实现更快(它是用 C 编写的,需要 C99 编译器)。

他们页面上的一个例子:

>>> def my_coroutine():
...     yield (yield (yield 1))
>>> from cotoolz import comap
>>> cm = comap(lambda a: a + 1, my_coroutine())
>>> next(cm)
2
>>> cm.send(2)
3
>>> cm.send(3)
4
>>> cm.send(4)
Traceback (most recent call last):
    ...
StopIteration

【讨论】:

    猜你喜欢
    • 2023-01-25
    • 2017-04-29
    • 2012-07-07
    • 1970-01-01
    • 2013-11-17
    • 2018-08-26
    • 1970-01-01
    • 2011-04-08
    • 1970-01-01
    相关资源
    最近更新 更多