Dlaczego Python nie zezwala na wieloliniowe lambdy?

50

Czy ktoś może wyjaśnić konkretne powody, dla których BDFL decyduje się na utworzenie pojedynczej linii dla lambda Python?

To jest dobre:

lambda x: x**x

Powoduje to błąd:

lambda x:
    x**x

Rozumiem, że stworzenie wieloliniowej linii lambda „zakłóciłoby” normalne zasady wcięć i wymagałoby dodania kolejnych wyjątków, ale czy nie jest to warte korzyści?

Spójrz na przykład na JavaScript. Jak żyć bez tych anonimowych funkcji? Są niezbędne. Czy Pythoniści nie chcą pozbyć się konieczności nazywania każdej funkcji wieloliniowej tylko po to, aby przekazać ją jako argument?

treecoder
źródło
3
Biorąc pod uwagę, że odnotowujesz konkretne powody, dla których Guido nie zezwala na lambdas z wieloma wyrażeniami, a następnie je odrzucasz, zakładam, że szukasz raczej potwierdzenia niż prawdziwej odpowiedzi.
Jason Baker
3
Czy oprócz uratowania siedmiu znaków jest to lepsze niż a def? Ma teraz dokładnie taką samą strukturę wizualną.
detly

Odpowiedzi:

42

Guido van van Rossum sam odpowiedział na to:

Jednak w takich rozwiązaniach często brakuje „Pythonicity” - tej nieuchwytnej cechy dobrej funkcji Python. Nie można wyrazić Pythonicity jako twardego ograniczenia. Nawet Zen Pythona nie przekłada się na prosty test Pythonicity ...

W powyższym przykładzie łatwo jest znaleźć piętę achillesową proponowanego rozwiązania: dwukropek, chociaż rzeczywiście jednoznacznie składniowo (jedno z „ograniczeń logicznych”), jest całkowicie arbitralny i nie przypomina niczego innego w Pythonie ...

Ale ja też to odrzucam, ponieważ ostatecznie (i tutaj przyznam się do mimowolnego wprowadzenia w błąd osoby zgłaszającej) uważam każde rozwiązanie za 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ą.

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

Zasadniczo mówi, że chociaż możliwe jest rozwiązanie, nie jest ono zgodne z tym, jak Python jest.

Maczuga
źródło
2
+1 dzięki za link do wątku - ale nadal byłbym wdzięczny za wieloliniowe lambdy - są bezcenne - spójrz na JavaScript, PHP też je zawiera.
treekoder
2
@greengit Możesz po prostu użyć zagnieżdżonego pliku def. To nie to samo co funkcje anonimowe, ale są wystarczająco blisko.
jsternberg
2
zagnieżdżone defs nie pomagają przy przejściu funkcji jako argumenty - to numer jeden powód, dla którego chciałbym multi-linię lambds
treecoder
1
@greengit - Myślę, że lepiej jest zająć się tym w GvR niż zamieszczać tutaj swoje komentarze.
Jason Baker,
9
@greengit: Czy wiesz, że możesz przekazać funkcję jako argument do innej funkcji? Nie możesz napisać tego bezpośrednio, ale nie ma dostępnej techniki programowania.
btilly,
24

doskonale jest wykonywać wieloliniową lambda w pythonie: patrz

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

prawdziwym ograniczeniem lambda jest fakt, że lambda musi być pojedynczym wyrażeniem ; nie może zawierać słowa kluczowego (jak python2 printlub return).

GvR decyduje się to zrobić, aby ograniczyć rozmiar lambda, ponieważ zwykle są one używane jako parametry. Jeśli chcesz prawdziwą funkcję, użyjdef

Vito De Tullio
źródło
1
multi line dotyczy wstawiania znaku „\ n”: D Python nie ma wielu instrukcji lambda. Naprawdę chcesz użyć def. Pomyśl o tym: naprawdę potrzebujesz wywoływalnego parametru jako parametru swojej funkcji? I użytkownicy tej funkcji nie mogą przekazać swoją domyślną wymagalne? Jak mogą je przekazać, jeśli im nie dasz?
Vito De Tullio,
btw, czy możesz podać przykład swojej anonimowej funkcji?
Vito De Tullio,
1
Tak, uważam, że ograniczenie jednego wyrażenia jest naprawdę frustrujące. To prawda, że ​​jeśli pozwolą na wyrażanie wielu wyrażeń, ludzie z pewnością zaczną je nadużywać, ale odwrotnie jest zbyt restrykcyjne omho.
rbaleksandar
10

Wiem, że to jest bardzo stare, ale umieszczam tutaj jako odniesienie.

Alternatywą dla zastosowania lambda może być użycie defw niekonwencjonalny sposób. Celem jest przekazanie deffunkcji, która może być wykonana tylko w jednej sytuacji - dekoratora. Zauważ, że dzięki tej implementacji def resultnie tworzona jest funkcja, lecz wynik reduce(), który kończy się na dict.

Bezwstydna wtyczka : robię dużo tego tutaj .

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

Zauważ, że lambd z wieloma wyrażeniami można zrobić, ale tylko z naprawdę, naprawdę brzydkim kodem. Interesujące jest jednak to, w jaki sposób scoping działa z tą implementacją (zwróć uwagę na wielokrotne użycie namezmiennej i zacienianie messagezmiennej).

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!
pirospada
źródło
+1 za podejście monadyczne
jozefg
Monady są również nazywane annables lub przyszłe / obietnice, a nawet zwrotne w JavaScript BTW.
aoeu256
3

Zebranie razem lambda z wieloma wyrażeniami nie jest tak złe, jak się wydaje pirospada: pewnie moglibyśmy skomponować kilka funkcji monadycznych za pomocą binda, jak w Haskell, ale skoro jesteśmy w nieczystym świecie Pythona, równie dobrze możemy użyj efektów ubocznych, aby osiągnąć to samo.

Na moim blogu opisuję kilka sposobów, aby to zrobić .

Na przykład Python gwarantuje, że elementy krotki będą oceniane w kolejności, dzięki czemu możemy używać ,podobnie jak imperatywu ;. Możemy zastąpić wiele printwyrażeń, takich jak , wyrażeniami, takimi jak sys.stdout.write.

Dlatego następujące są równoważne:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

Zauważ, że dodałem Nonena końcu i wyodrębniłem go za pomocą [-1]; ustawia to wyraźnie wartość zwracaną. Nie musimy tego robić, ale bez tego uzyskalibyśmy funky (None, None, None)wartość zwrotną, o którą moglibyśmy dbać lub nie.

Możemy więc sekwencjonować działania IO. Co ze zmiennymi lokalnymi?

Python =tworzy zdanie, więc musimy znaleźć równoważne wyrażenie. Jednym ze sposobów jest mutowanie zawartości struktury danych, przekazywanej jako argument. Na przykład:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

Jest kilka sztuczek używanych w stateful_lambda:

  • *_Argument umożliwia naszym lambda podjąć jakąkolwiek liczbę argumentów. Ponieważ pozwala to na zerowe argumenty, odzyskujemy konwencję wywoływania z stateful_def.
    • Wywołanie argumentu _to tylko konwencja, która mówi: „Nie zamierzam używać tej zmiennej”
  • Mamy jedną funkcję („wrapper”) zwracającą inną funkcję („main”): lambda state: lambda *_: ...
    • Dzięki zakresowi leksykalnemu argument pierwszej funkcji będzie znajdował się w zasięgu drugiej funkcji
    • Zaakceptowanie niektórych argumentów teraz i zwrócenie innej funkcji, aby zaakceptować resztę później, nazywa się curry
  • Natychmiast wywołujemy funkcję „otoki”, przekazując jej pusty słownik: (lambda state: ...)({})
    • To pozwala nam przypisać zmienną statedo wartości {}bez użycia instrukcji przypisania (np. state = {})
  • Klucze i wartości traktujemy statejak nazwy zmiennych i wartości powiązane
    • Jest to mniej kłopotliwe niż stosowanie natychmiastowych lambdów
    • To pozwala nam mutować wartości zmiennych
    • Używamy state.setdefault(a, b)zamiast a = bi state.get(a)zamiasta
  • Używamy krotki, aby połączyć nasze skutki uboczne, tak jak wcześniej
  • Używamy [-1]do wyodrębnienia ostatniej wartości, która działa jak returninstrukcja

Oczywiście jest to dość kłopotliwe, ale możemy stworzyć ładniejszy interfejs API z funkcjami pomocniczymi:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])
Warbo
źródło
żartujesz, to wygląda jak koszmar lol
Alexander Mills
1
@AlexanderMills Heh, to nie było zamierzone jako rzeczywisty przykład, bardziej jako obalenie podejścia lambda-in-lambda-in-lambda pirospady, aby pokazać, że nie jest tak źle. W rzeczywistości można to znacznie uprościć teraz, gdy mamy python.org/dev/peps/pep-0572
Warbo
1

Myślałem, że mógłbym się przyłączyć, użyć przerywacza linii:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

Nie zapominaj o bardzo miłej rzeczy, jaką Python potrafi pisać oneliner, jak na przykład:

a=b=0; c=b+a; d = a+b**2 #etc etc

I lambda jest bardzo potężna, ale nie jest przeznaczona do zastąpienia 1 całej funkcji, to znaczy możesz ją zhakować (przykład pożyczenia od kolegi powyżej):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

Ale czy naprawdę chcesz to zrobić w ten sposób? Po pewnym czasie jest to w większości nieczytelne, to jak dotarcie do początku liny, zaczynając od nierozwiniętego końca. rozwikłana lina

Lambdas są traktowane jako funkcje jednorazowe, w mapowaniu, filtrowaniu i zmniejszaniu funkcji w funkcyjnym programowaniu (między innymi). Na przykład uzyskanie wartości znakowych wartości, które są liczbami całkowitymi i podzielnymi przez 2

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

Możesz użyć go jako funkcji wyrażania funkcji, definiując złożoną funkcję (lub więcej i kilka lambd, i umieszczając ją w innej lambda:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

ale Python ma obsługę wyrażeń funkcji w inny sposób: -lets mówi, że masz wywoływaną funkcję superAwesomeFunctioni że ta funkcja może robić super niesamowite rzeczy, możesz przypisać ją do zmiennej, nie wywołując jej, tak jak to:

SAF = superAwesomeFunction # there is no () at the end, 

Więc teraz, gdy wywołasz SAF, wywołasz superAwesomeFunction lub metodę. Jeśli przeszukujesz folder Lib, możesz zauważyć, że większość __builtin__modułów Pythona jest napisana w ten sposób. Dzieje się tak, ponieważ czasami będziesz potrzebować niektórych funkcji, które wykonują określone zadania, które nie są wystarczająco potrzebne, aby były użyteczne dla użytkownika, ale są niezbędne dla kilku funkcji. Zatem masz wybór, że nie możesz mieć 2 funkcji o nazwie „superAwesomeFunction”, możesz mieć „superAwesomeFunctionDoingBasicStuf” i „realSuperAwesomeFunction”, a następnie po prostu wstawić „realSuperAwesomeFunction” w zmiennej „superAwesomeFunction” i gotowe.

Możesz znaleźć lokalizację zaimportowanych modułów, wchodząc do konsoli importedModule.__file__(prawdziwy przykład import os;os.__file__), i po prostu podążaj za tym katalogiem do pliku o nazwie importModule.py i otwórz go w edytorze i dowiedz się, jak zmaksymalizować własną „wiedzę”.

Mam nadzieję, że to pomoże tobie i innym współpracownikom w tarapatach.

Danilo
źródło