0

Как работает это выражение с лямбдой/yield/генератором?

55

Я просмотрел свой код сегодня и наткнулся на следующий фрагмент:

def optionsToArgs(options, separator='='):
    kvs = [
        (
            "%(option)s%(separator)s%(value)s" %  
            {'option' : str(k), 'separator' : separator, 'value' : str(v)}
        ) for k, v in options.items()
    ]
    return list(
        reversed(
            list(
                    (lambda l, t: 
                        (lambda f: 
                            (f((yield x)) for x in l)
                        )(lambda _: t)
                    )(kvs, '-o')
                )
            )
        )

Похоже, что эта функция принимает словарь параметров и преобразует его в список параметров для командной строки. Однако я заметил, что здесь используется yield внутри генератора, что, как мне казалось, невозможно...?

>>> optionsToArgs({"x":1,"y":2,"z":3})
['-o', 'z=3', '-o', 'x=1', '-o', 'y=2']

Как это работает?

1 ответ(ов)

0

Похоже, вы столкнулись с интересной ситуацией, связанной с использованием yield в генераторах. Давайте разберемся.

Ваш код представляет два уровня генерации: первый — это thegenerator, который использует yield, чтобы возвращать элементы из списка kvs, а второй — это функция f, которая просто возвращает строку '-o'.

Вот как это работает:

def f(_):  # Это эквивалентно lambda _: '-o'
    return '-o'

def thegenerator():  # Это похоже на (f((yield x)) for x in l)
    for x in kvs:
        yield f((yield x))

Когда мы итерируемся по thegenerator, он поочередно:

  1. Возвращает x — элемент из kvs.
  2. Ждет, пока не будет передано значение через yield x, и передает это значение в f.
  3. Возвращает результат f, который, как вы отметили, всегда '-o'.

При этом важно понимать, что значение, возвращаемое yield x, игнорируется.

Вы также привели несколько эквивалентных вариантов:

def thegenerator():
    for x in kvs:
        whatever = (yield x)
        yield f(whatever)

Здесь значение whatever игнорируется так же, как и в оригинальном варианте, так как мы просто передаем его в f и далее возвращаем результат.

Далее предлагается упрощенный вариант:

def thegenerator():
    for x in kvs:
        yield x
        yield f(None)

Или еще проще:

def thegenerator():
    for x in kvs:
        yield x
        yield '-o'

Таким образом, существует множество более простых способов достичь той же функциональности, чем использовать двойной yield. Например, вы можете просто сделать:

return list(((lambda _: '-o')((yield x)) for x in kvs))[::-1])

Однако это может быть не совсем очевидно для чтения. Так что, в зависимости от контекста и целевой аудитории, вы можете выбрать тот подход, который вашему мнению будет наиболее понятен и удобен. Надеюсь, это поможет вам!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь