Как работает это выражение с лямбдой/yield/генератором?
Я просмотрел свой код сегодня и наткнулся на следующий фрагмент:
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 ответ(ов)
Похоже, вы столкнулись с интересной ситуацией, связанной с использованием 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
, он поочередно:
- Возвращает
x
— элемент изkvs
. - Ждет, пока не будет передано значение через
yield x
, и передает это значение вf
. - Возвращает результат
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])
Однако это может быть не совсем очевидно для чтения. Так что, в зависимости от контекста и целевой аудитории, вы можете выбрать тот подход, который вашему мнению будет наиболее понятен и удобен. Надеюсь, это поможет вам!
Сравнение: генераторы списков против lambda + filter
mysql_config не найден при установке интерфейса mysqldb для Python
Что делает yield без значения в контекстном менеджере?
Цикл for внутри лямбды
Python: использовать `yield from` или вернуть генератор?