Jak działa zrozumienie lambda/wydajność/generator?

Przeglądałem dzisiaj mój kod i znalazłem to:

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

Wydaje się, że pobiera dict parametrów i zamienia je w listę parametrów dla polecenia powłoki. Wygląda na to, że wykorzystuje wydajność wewnątrz generatora, co wydawało mi się niemożliwe...?

>>> optionsToArgs({"x":1,"y":2,"z":3})
['-o', 'z=3', '-o', 'x=1', '-o', 'y=2']
Jak to działa?
Author: J0HN, 2013-04-11

2 answers

Od wersji Python 2.5, yield <value> jest wyrażeniem, a nie instrukcją. Zobacz PEP 342 .

Kod jest ohydny i niepotrzebnie brzydki, ale jest legalny. Jego centralnym trikiem jest użycie f((yield x)) wewnątrz wyrażenia generatora. Oto prostszy przykład tego, jak to działa:

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

Zasadniczo, użycie yield w wyrażeniu generatora powoduje, że generuje on dwie wartości dla każdej wartości w źródle iterowalnym. Gdy wyrażenie generatora jest powtarzane nad listą łańcuchów, na każdym iteracja, yield x najpierw daje ciąg znaków z listy. Wyrażeniem docelowym genexp jest f((yield x)), więc dla każdej wartości na liście "wynik" wyrażenia generatora jest wartością f((yield x)). Ale f po prostu ignoruje swój argument i zawsze zwraca ciąg opcji "-o". Tak więc na każdym kroku generowania generuje najpierw łańcuch klucz-wartość (np. "x=1"), a następnie "-o". Zewnętrzny list(reversed(list(...))) tworzy listę z tego generatora, a następnie odwraca ją tak, że "-o"S będzie przyjdź przed każdą opcją zamiast po.

Nie ma jednak powodu, by robić to w ten sposób. Istnieje wiele znacznie bardziej czytelnych alternatyw. Być może najbardziej wyraźne jest po prostu:
kvs = [...] # same list comprehension can be used for this part
result = []
for keyval in kvs:
   result.append("-o")
   result.append(keyval)
return result

Nawet jeśli lubisz zwięzły," sprytny " Kod, możesz po prostu zrobić

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

Samo rozumienie listy jest dziwaczną mieszanką próbnej czytelności i nieczytelności. Jest to po prostu napisane:

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

Powinieneś rozważyć zorganizowanie "interwencji" dla ktokolwiek umieścił to w Twojej bazie kodowej.

 47
Author: BrenBarn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-11 18:42:53

O Boże. W zasadzie sprowadza się to do tego:

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))

Więc gdy iteracja jest skończona, Generator daje x (członek kvs), a następnie wartość zwracaną f, która jest zawsze -o, wszystko w jednej iteracji nad kvs. Cokolwiek yield x zwróci i co zostanie przekazane f jest ignorowane.

Odpowiedniki:

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'

Istnieje wiele sposobów, aby zrobić to znacznie prostsze, oczywiście. Nawet z oryginalną sztuczką z podwójną wydajnością, cała rzecz mogła być

return list(((lambda _: '-o')((yield x)) for x in kvs))[::-1]
 19
Author: Pavel Anossov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-26 19:36:35