Условные возвращаемые значения - но с `yield from` вместо` return` - PullRequest
0 голосов
/ 08 апреля 2020

В Python часто пишут небольшие функции с таким типом управления потоком:

def get_iterstuff(source: Thingy, descr: Optional[str]) -> Iterable[Stuff]:
    """ Return an iterator of Stuff instances from a given Thingy """
    key = prepare_descr(descr)        # may be a “str”, may be None
    val = getattr(source, key, None)  # val has type Optional[Iterable[Stuff]]
    if not val:                       # None, or (in some cases) an empty
        return IterStuff.EMPTY        # sentinel, with the same type as val
    return val                        # the likeliest codepath

… где один оператор if может возвращаться раньше - в некоторых таких функциях раньше, чем любая из вызовы подготовительной функции, как в первых двух строках этого примера.

Это отлично работает. Я имею в виду, я полагаю, что существуют некоторые сложности, связанные с оптимизацией глазков, которые возникают в функциях с несколькими return утверждениями . Но в целом, как и многие другие Pythoneers, я делаю такие вещи буквально все время, чтобы производить тестируемые, поддерживаемые и легко читаемые функции управления потоком, которые можно запускать без какого-либо большого гнева.

Но так, мой вопрос касается того, каким образом эта логика c принимает несколько иную форму, например, когда функция является генератором, который использует один или несколько операторов yield from:

def iterstuff(source: Thingy, descr: Optional[str]) -> YieldFrom[Stuff]: # ‡
    key = prepare_descr(descr)          # may be a “str”, may be None
    val = getattr(source, key, None)    # val has type Optional[Iterable[Stuff]]
    if not val:
        yield from tuple()              # … or suchlike
    else:
        yield from val                  # the likeliest codepath

… В этой версии сразу большие выводы:

  1. Второй оператор yield from, который формирует конец наиболее вероятного пути кода функции, находится в предложении else, а не в верхний уровень блока кода функций.
  2. Это странное использование конструктора tuple(…) для yield from чего-то пустого и повторяемого.

… Итак, я знаю, что использование else обусловлено тем фактом, что элемент управления падает через операторы yield и yield from после их исчерпания (что является что в других случаях я люблю использовать и ругать). А вот вставить вещь tuple(…) гораздо проще, чем, например, всю работу, которую пришлось бы go придумать для того типа часового, которого IterStuff.EMPTY из первой функции было бы.

Но этот yield from пример выглядит опаснее, чем его return аналог. Больше от 1059 *. Менее обдуманный. Вонючий код, если хотите.

Итак, я спрашиваю: Какой самый разборчивый, наименее последовательный и оптимально наиболее Pythoni c способ структурировать версию yield from этого?

Это tuple() код-бродяга в порядке, или есть менее программно пахучие альтернативы?

Существуют ли лучшие способы для управления потоком, как это? Обременены ли какие-либо из них (или любой из моих примеров) проблемными c временными сложностями?

† - (а именно то, что их трудно оптимизировать для глазка; я бы не знал - дизайн компилятора намного выше моего paygrade)

‡ - псевдоним типа «YieldFrom» c упрощает аннотирование этих функций генератора - так как typing.Generator немного выше, как написано. Это выглядит так:

class YieldFrom(Generator[T_co, None, None]):
    """ Simple “typing.Generator” alias. The generic Generator
        from “typing” requires three type params:

            • “yield_type” (covariant),
            • “send_type” (contravariant), and
            • “return_type” (covariant).

        … a function containing 1..n “yield” or “yield from”
        statements – without returning anything and unburdened
        by any expectations of calls to its “send(…)” method
        showing up in any type-festooned code – can make use
        of this “YieldFrom[T]” alias. ÷Explict beats implict÷
    """
    pass
...