Изменение доходности от возвращаемого значения

Допустим, у меня есть эти парсеры:

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

parse_foo а также parse_bar оба генератора, которые выдают строки по одной. Если я хочу создать одну функцию отправки, я бы сделал это:

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

Выход из синтаксиса позволяет мне легко туннелировать информацию вверх и вниз по генераторам.

Есть ли способ сохранить туннелирование при изменении результатов урожайности?
Сделать это, преодолев туннелирование, легко:

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(), Это уродливо, грязно и подвержено ошибкам.

3 ответа

Решение

К сожалению, нет встроенного, который делает это. Вы можете реализовать это самостоятельно, используя классы, но пакет cotoolz реализует map() функция, которая делает именно это.

Их функция отображения в 4 раза медленнее встроенной map() но он осведомлен о протоколе генератора и быстрее, чем аналогичная реализация 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

Есть другой способ сделать это, кроме 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, но это не обязательно в этом случае использования.

Есть parse_foo а также parse_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"
Другие вопросы по тегам