web-dev-qa-db-ja.com

このラムダ/収量/発電機の理解はどのように機能しますか?

今日、コードベースを調べていたところ、これが見つかりました。

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']

どのように機能しますか?

51
Dog

Python 2.5、_yield <value>_はステートメントではなく式です。 PEP 342 を参照してください。

コードは恐ろしく不必要にいですが、合法です。その中心的なトリックは、ジェネレーター式内でf((yield x))を使用することです。これがどのように機能するかのより簡単な例は次のとおりです。

_>>> def f(val):
...     return "Hi"
>>> x = [1, 2, 3]
>>> list(f((yield a)) for a in x)
[1, 'Hi', 2, 'Hi', 3, 'Hi']
_

基本的に、ジェネレータ式でyieldを使用すると、ソース反復可能のすべての値に対して2つの値が生成されます。ジェネレータ式は文字列のリストを反復処理するため、各反復で_yield x_が最初にリストから文字列を生成します。 genexpのターゲット式はf((yield x))であるため、リスト内のすべての値について、ジェネレーター式の「結果」はf((yield x))の値です。ただし、fは引数を無視し、常にオプション文字列_"-o"_を返します。そのため、ジェネレーターを通過するすべてのステップで、最初にキーと値の文字列(例、_"x=1"_)、次に_"-o"_が生成されます。外側のlist(reversed(list(...)))はこのジェネレーターからリストを作成し、それを逆にして、_"-o"_ sが各オプションの後ではなく前に来るようにします。

ただし、この方法で行う理由はありません。より多くの読みやすい代替があります。おそらく最も明示的なのは単純です:

_kvs = [...] # same list comprehension can be used for this part
result = []
for keyval in kvs:
   result.append("-o")
   result.append(keyval)
return result
_

簡潔で「賢い」コードが好きな場合でも、あなたはまだやることができます

_return sum([["-o", keyval] for keyval in kvs], [])
_

kvsリスト内包表記自体は、読みやすさと読み難さの奇妙な組み合わせです。より簡単に書かれています:

_kvs = [str(optName) + separator + str(optValue) for optName, optValue in options.items()]
_

これをコードベースに入れた人には「介入」を手配することを検討する必要があります。

48
BrenBarn

うん基本的に、これは次のように要約されます。

def f(_):              # I'm the lambda _: t
    return '-o'

def thegenerator():   # I'm (f((yield x)) for x in l)
    for x in kvs:
        yield f((yield x))

そのため、反復処理を行うと、ジェネレーターはxkvsのメンバー)を生成し、次にfの戻り値(常に-o、すべてkvsに対する1回の反復で。なんでも yield xが戻り、fに渡されるものは無視されます。

同等のもの:

def thegenerator():   # I'm (f((yield x)) for x in l)
    for x in kvs:
        whatever = (yield x)
        yield f(whatever)

def thegenerator():   # I'm (f((yield x)) for x in l)
    for x in kvs:
        yield x
        yield f(None)

def thegenerator():   # I'm (f((yield x)) for x in l)
    for x in kvs:
        yield x
        yield '-o'

もちろん、これをもっと簡単にする方法はたくさんあります。元のダブルイールドトリックを使用しても、全体が

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