Brak Multiline Lambda w Pythonie: dlaczego nie?

335

Słyszałem, że w Pythonie nie można dodawać wielowierszowych lambd, ponieważ kolidują one składniowo z innymi konstrukcjami składni w Pythonie. Myślałem o tym dzisiaj w autobusie i zdałem sobie sprawę, że nie mogę wymyślić żadnego konstruktu w języku Python, z którym kolidują wielowierszowe lambdy. Biorąc pod uwagę, że znam język dość dobrze, to mnie zaskoczyło.

Teraz jestem pewien, że Guido miał powód, by nie uwzględniać wielowierszowych lambd w języku, ale z ciekawości: w jakiej sytuacji uwzględnienie wielowierszowej lambdy byłoby niejednoznaczne? Czy to, co słyszałem, jest prawdą, czy jest jakiś inny powód, dla którego Python nie zezwala na wieloliniowe lambdy?

Imagist
źródło
12
wersja tl; dr: ponieważ Python jest leniwym językiem bez bloków {}, więc nie było to dozwolone w celu zachowania spójnego projektu składniowego.
Andrew
11
Ponadto: Jestem całkowicie zaskoczony, że nikt nie wspomniał o tym w odpowiedziach ... Możesz zakończyć wiersze znakiem \ w Pythonie i przejść do następnego wiersza ... Ta informacja w pewnym sensie zastępuje całe to pytanie, więc ...
Andrew
„projekt syntaktyczny”
nicolas
Wymagałoby to zezwolenia na wyrażenia w wyrażeniach. Jeśli masz zamiar to zrobić, nie potrzebujesz lambdaprzede wszystkim wyrażeń; możesz po prostu użyć defwyrażeń.
chepner

Odpowiedzi:

153

Spójrz na następujące:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

Czy to zwraca lambda (y, [1,2,3])(dlatego mapa otrzymuje tylko jeden parametr, co powoduje błąd)? Czy to zwraca y? Czy jest to błąd składniowy, ponieważ przecinek w nowym wierszu jest nieprawidłowo umieszczony? Skąd Python wiedziałby, czego chcesz?

W obrębie parens wcięcie nie ma znaczenia dla Pythona, więc nie można jednoznacznie pracować z multiliniami.

To jest po prostu prosty, prawdopodobnie jest więcej przykładów.

balpha
źródło
107
mogą wymusić użycie nawiasów, jeśli chcesz zwrócić krotkę z lambda. IMO powinno to zawsze być egzekwowane, aby zapobiec takim dwuznacznościom, ale no cóż.
mpen
26
Jest to prosta dwuznaczność, którą należy rozwiązać, dodając dodatkowy zestaw parens, coś, co już istnieje w wielu miejscach, np. Wyrażenia generatora otoczone innymi argumentami, wywołując metodę na literałach liczb całkowitych (chociaż nie musi tak być, ponieważ nazwa funkcji nie może zaczynać się od cyfry) i oczywiście również lambda jednowierszowe (które mogą być długimi wyrażeniami zapisanymi w wielu wierszach). Lambda wieloliniowe nie różni się szczególnie od tych przypadków, które uzasadniają wyłączenie ich na tej podstawie. To jest prawdziwa odpowiedź.
nmclean
3
Podoba mi się to, że istnieją języki gazillionowe, które radzą sobie z tym bez obaw, ale jakoś istnieją głębokie powody, dla których podobno jest to bardzo trudne, jeśli nie niemożliwe
nicolas
1
@nicolas, który pyton w pigułce
javadba,
Powód, dla którego nie używam lambda, tak słabo rozwiniętego w Pythonie.
NoName
635

Guido van Rossum (wynalazca Pythona) sam odpowiada na dokładnie to pytanie w starym poście na blogu .
Zasadniczo przyznaje, że jest to teoretycznie możliwe, ale każde proponowane rozwiązanie nie byłoby Pythonic:

„Jednak złożoność każdego proponowanego rozwiązania tej zagadki jest dla mnie ogromna: wymaga ona, aby parser (a ściślej leksykon) mógł przełączać się między trybami wrażliwymi na wcięcia i niewrażliwymi na wcięcia, zachowując stos poprzednich trybów i poziomu wcięcia. Technicznie wszystko to można rozwiązać (istnieje już stos poziomów wcięć, które można uogólnić). Ale to nie odbiera mi przeczucia, że ​​to wszystko jest skomplikowanym urządzeniem Rube Goldberga . ”

Eli Courtwright
źródło
108
Dlaczego nie jest to najlepsza odpowiedź? Nie chodzi o powody techniczne, to wybór projektowy, jak wyraźnie stwierdził wynalazca.
Dan Abramov,
13
@DanAbramov, ponieważ OP nie logował się prawdopodobnie przez lata.
Umowa prof. Falkena została naruszona
7
Dla tych, którzy nie rozumieli odniesienia do Rube Goldberg, patrz: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj
56
Odpowiedź Guido to kolejny powód, dla którego chciałbym, aby Python nie polegał na wcięciach w definiowaniu bloków.
LS
25
Nie jestem pewien, czy nazwałbym „przeczuciem” wyborem projektowym. ;)
Elliot Cameron,
54

Jest to na ogół bardzo brzydkie (ale czasami alternatywy są jeszcze bardziej brzydkie), więc obejście polega na utworzeniu wyrażenia nawiasów klamrowych:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

Nie przyjmie jednak żadnych zadań, więc musisz wcześniej przygotować dane. Miejsce, które uznałem za przydatne, to opakowanie PySide, w którym czasami masz krótkie oddzwanianie. Pisanie dodatkowych funkcji składowych byłoby jeszcze bardziej brzydkie. Zwykle nie będziesz tego potrzebować.

Przykład:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Sebastian Bartos
źródło
2
Mój szef właśnie prosił o coś takiego w naszej aplikacji PyQt. Niesamowite!
TheGerm
1
Dzięki za to szukałem również dobrego sposobu na użycie krótkich (ale wciąż wielowierszowych) lambd jako wywołań zwrotnych dla naszego interfejsu PySide.
Michael Leonard,
A teraz to widziałem, od razu sugeruje użycie lambda argi setattr(arg, 'attr','value')obalenie „żadnych zadań ...”. A potem jest ocena zwarcia andi or... robi to JavaScript. Wrzuca w ciebie korzenie, jak bluszcz w ścianę. Mam prawie nadzieję, że zapomnę o tym w Boże Narodzenie.
nigel222,
całkiem sprytne - i dość czytelne. Teraz - o tych (brakujących ...) zadaniach ..
javadba
@ nigel222 po co się wstydzić? Język Python jest całkowicie sparaliżowana - ale jest to jedna używane tak czy inaczej za dużo nauki danych . Więc dokonujemy korekt. Znalezienie sposobów na wywołanie efektów ubocznych (często po prostu drukowanie / rejestrowanie!) I zadań (często wystarczających tylko do pośrednich zmiennych!) Powinno być dobrze obsługiwane przez język. Ale nie są nawet obsługiwane (przynajmniej jeśli postępujesz zgodnie z PEPwytycznymi)
javadba
17

Kilka odpowiednich linków:

Przez jakiś czas śledziłem rozwój Reii, która początkowo miała mieć składnię Pythona opartą na wcięciach z blokami Ruby, wszystko na Erlangu. Ale projektant zrezygnował z wrażliwości na wcięcia, a ten post, który napisał o tej decyzji, zawiera dyskusję o problemach, na które natrafił z wcięciami + bloki wieloliniowe, oraz zwiększone uznanie dla problemów / decyzji projektowych Guido:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

Oto też interesująca propozycja bloków w stylu Ruby w Pythonie, gdzie natknąłem się na miejsce, w którym Guido publikuje odpowiedź bez jej zestrzelenia (choć nie jestem pewien, czy nastąpiła kolejna strzelanina):

http://tav.espians.com/ruby-style-blocks-in-python.html

Zaraz
źródło
12

[Edytuj] Przeczytaj tę odpowiedź. Wyjaśnia, dlaczego lambda wieloliniowa nie jest rzeczą.

Mówiąc najprościej, to nie jest mityczne. Z postu na blogu Guido van Rossuma:

Uważam, że każde rozwiązanie jest niedopuszczalne, które osadza blok oparty na wcięciach w środku wyrażenia. Ponieważ alternatywną składnię dla grupowania instrukcji (np. Nawiasy klamrowe lub słowa kluczowe rozpoczynające / kończące) uważam za równie niedopuszczalną, to w zasadzie sprawia, że ​​lambda wieloliniowa jest nierozwiązywalną łamigłówką.

Samy Bencherif
źródło
10

Pozwól, że przedstawię ci chwalebny, ale przerażający hack:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

Możesz teraz użyć tego LETformularza jako takiego:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

co daje: [0, 3, 8]

divs1210
źródło
1
Pierwotnie opublikowane na gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210
To jest niesamowite! Myślę, że użyję tego następnym razem, gdy napiszę Python. Jestem przede wszystkim programistą Lisp i JS, a boli mnie brak wielu linii lambada. Jest to sposób na uzyskanie tego.
Christopher Dumas
7

Jestem winny praktykowania tego brudnego hacka w niektórych moich projektach, co jest nieco prostsze:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

Mam nadzieję, że znajdziesz sposób, by pozostać pytonicznym, ale jeśli musisz to zrobić mniej bolesne niż używanie exec i manipulowanie globalsami.

S.Rad
źródło
6

Pozwól mi spróbować rozwiązać problem z analizowaniem @balpha. Używałbym nawiasów wokół wieloliniowej Lamdy. Jeśli nie ma nawiasów, definicja lambda jest zachłanna. Więc lambda w

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

zwraca funkcję, która zwraca (y*z, [1,2,3])

Ale

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

znaczy

map(func, [1,2,3])

gdzie func to wielowierszowa lambda, która zwraca y * z. Czy to działa?

Wai Yip Tung
źródło
1
Myślę, że górny powinien powrócić, map(func, [1,2,3])a dolny powinien być błędem, ponieważ funkcja mapy nie ma wystarczającej liczby argumentów. W kodzie jest też kilka dodatkowych nawiasów.
Samy Bencherif,
upuszczenie tego do pycharm z uruchomionym python2.7.13 daje błąd składniowy.
simbo1905,
dodatkowy nawias
Samy Bencherif
4

(Dla każdego, kto nadal jest zainteresowany tym tematem.)

Zastanów się nad tym (obejmuje nawet użycie zwracanych wartości instrukcji w dalszych instrukcjach w „multilinicznej” lambdzie, chociaż jest to brzydkie do tego stopnia, że ​​wymiotuje ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
vencik
źródło
To nie działa, gdy wywoływany jest drugi raz z różnymi parametrami i powoduje wyciek pamięci, chyba że pierwszy wiersz jest, state.clear()ponieważ domyślne argumenty są tworzone tylko raz, gdy funkcja jest tworzona.
Matthew D. Scholefield,
1

Możesz po prostu użyć slash ( \), jeśli masz wiele linii dla funkcji lambda

Przykład:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30
IRSHAD
źródło
Pytanie dotyczy samego użycia więcej niż 1 wyrażenia niż więcej niż 1 linii literalnej.
Tomas Zubiri
1

Zaczynam od Pythona, ale najbardziej oczywistym sposobem jest wyodrębnienie wyrażenia jako funkcji ....

Skonstruowany przykład, wyrażenie wielokrotne (x*2)jest wyodrębniane jako funkcja i dlatego mogę używać multilinii:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Być może nie odpowiada to dokładnie na pytanie, czy to był sposób wykonywania multilinii w samym wyrażeniu lambda , ale na wypadek, gdyby ktoś dostał ten wątek i szukał sposobu debugowania wyrażenia (jak ja), myślę, że to pomoże

Vicens Fayos
źródło
2
Dlaczego miałbym to robić, a nie pisać map(multiply, [1, 2, 3])?
thothal
0

Jeśli chodzi o brzydkie hacki, zawsze możesz użyć kombinacji execfunkcji zwykłej i zwykłej, aby zdefiniować funkcję multilinii w następujący sposób:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

Możesz zawinąć to w funkcję taką jak:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')
Matthew D. Scholefield
źródło
0

Po prostu grałem trochę, aby spróbować zrozumieć dyktowanie z redukcją i wymyślić ten jeden liniowy hack:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

Właśnie próbowałem zrobić to samo, co zrobiono w tym zrozumieniu języka JavaScript: https://stackoverflow.com/a/11068265

rodfersou
źródło
0

Oto bardziej interesujące wdrożenie wielu linii lambd. Nie jest to możliwe ze względu na to, jak Python używa wcięć jako sposobu strukturyzacji kodu.

Ale na szczęście dla nas formatowanie wcięć można wyłączyć za pomocą tablic i nawiasów.

Jak niektórzy już zauważyli, możesz napisać kod jako taki:

lambda args: (expr1, expr2,... exprN)

Teoretycznie, jeśli masz gwarancję oceny od lewej do prawej, to zadziałałoby, ale nadal tracisz wartości przekazywane z jednego wyrażenia na drugie.

Jednym ze sposobów osiągnięcia tego, co jest nieco bardziej szczegółowe, jest posiadanie

lambda args: [lambda1, lambda2, ..., lambdaN]

Gdzie każda lambda otrzymuje argumenty od poprzedniego.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

Ta metoda pozwala napisać coś, co jest nieco seplenienie / schematem.

Możesz więc pisać takie rzeczy:

let(lambda x, y: x+y)((1, 2))

Do obliczenia przeciwprostokątnej można zastosować bardziej złożoną metodę

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

Spowoduje to zwrócenie listy liczb skalarnych, dzięki czemu można jej użyć do zmniejszenia wielu wartości do jednej.

Posiadanie tak wielu lambda z pewnością nie będzie bardzo wydajne, ale jeśli jesteś ograniczony, może to być dobry sposób na szybkie zrobienie czegoś, a następnie przepisanie go jako rzeczywistej funkcji później.

Loïc Faure-Lacroix
źródło
-3

ponieważ funkcja lambda ma być jednowierszowa, ponieważ jest to najprostsza forma funkcji, an entrance, then return

Sphynx-HenryAY
źródło
1
Nie, każdy, kto napisze program sterowany zdarzeniami, powie ci, że multiline lambda jest ważna.
Akangka