Mengapa Python tidak mengizinkan lambda multi-line?

50

Adakah yang bisa menjelaskan alasan konkret mengapa BDFL memilih untuk membuat Python lambdas satu baris?

Ini bagus:

lambda x: x**x

Ini menghasilkan kesalahan:

lambda x:
    x**x

Saya mengerti bahwa membuat lambda multi-baris entah bagaimana akan "mengganggu" aturan lekukan normal dan akan membutuhkan menambahkan lebih banyak pengecualian, tetapi bukankah itu sepadan dengan manfaatnya?

Lihatlah JavaScript, misalnya. Bagaimana seseorang bisa hidup tanpa fungsi-fungsi anonim itu? Mereka sangat diperlukan. Bukankah Pythonistas ingin menyingkirkan keharusan untuk menamai setiap fungsi multi-line hanya untuk melewatinya sebagai argumen?

treecoder
sumber
3
Mengingat Anda mencatat alasan konkret mengapa Guido tidak mengizinkan multi-ekspresi lambda dan kemudian mengabaikannya, saya akan menganggap Anda mencari validasi daripada jawaban nyata.
Jason Baker
3
Selain menyimpan tujuh karakter, bagaimana ini lebih baik daripada a def? Sekarang memiliki struktur visual yang persis sama.
detly

Jawaban:

42

Guido van van Rossum menjawab sendiri:

Tetapi solusi seperti itu sering kali tidak memiliki "Pythonicity" - sifat yang sulit dipahami dari fitur Python yang baik. Tidak mungkin untuk mengekspresikan Pythonicity sebagai kendala yang sulit. Bahkan Zen Python tidak diterjemahkan menjadi tes sederhana Pythonicity ...

Dalam contoh di atas, mudah untuk menemukan tumit Achilles dari solusi yang diusulkan: usus besar ganda, walaupun secara sintaksis tidak ambigu (salah satu "kendala teka-teki"), sepenuhnya arbitrer dan tidak menyerupai apa pun dalam Python ...

Tapi saya menolak itu juga, karena pada akhirnya (dan ini adalah di mana saya mengaku tidak sengaja menyerahkan submitter), saya menemukan solusi yang tidak dapat diterima yang menanamkan blok berbasis lekukan di tengah-tengah ekspresi. Karena saya menemukan sintaks alternatif untuk pengelompokan pernyataan (mis. Kawat gigi atau kata kunci mulai / akhir) sama-sama tidak dapat diterima, ini membuat lambda multi-baris menjadi teka-teki yang tidak dapat diselesaikan.

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

Pada dasarnya, dia mengatakan bahwa meskipun solusi mungkin, itu tidak sesuai dengan bagaimana Python.

Selikuran
sumber
2
Terima kasih +1 atas utas tautan - tetapi saya masih akan menghargai lambda multi-baris - mereka sangat berharga - lihat JavaScript, PHP sudah memasukkannya juga.
treecoder
2
@greengit Anda bisa menggunakan def bersarang. Ini tidak sama dengan fungsi anonim, tetapi mereka cukup dekat.
jsternberg
2
def bersarang tidak membantu ketika melewati fungsi sebagai argumen - itulah alasan nomor satu mengapa saya ingin multi-line lambds
treecoder
1
@ Grengeng - Saya pikir Anda lebih baik mengambil ini dengan GvR daripada memposting komentar Anda di sini.
Jason Baker
9
@ greengit: Anda tahu bahwa Anda dapat meneruskan fungsi sebagai argumen ke fungsi lain? Anda tidak dapat menulisnya sebaris, tetapi tidak ada teknik pemrograman yang tidak tersedia untuk Anda.
btilly
24

tidak apa-apa untuk melakukan multi-baris lambda di python: lihat

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

batasan lambda yang sebenarnya adalah fakta bahwa lambda harus merupakan ungkapan tunggal ; tidak dapat berisi kata kunci (seperti python2 printatau return).

GvR memilih untuk melakukannya untuk membatasi ukuran lambda, karena biasanya digunakan sebagai parameter. Jika Anda ingin fungsi yang sebenarnya, gunakandef

Vito De Tullio
sumber
1
multi baris adalah tentang penyisipan karakter '\ n': D python tidak memiliki multi- pernyataan lambda. Anda benar-benar ingin menggunakannya def. Pikirkan tentang hal ini: Anda benar-benar perlu dipanggil sebagai parameter fungsi Anda? Dan pengguna fungsi itu tidak diizinkan melewati panggilan default Anda ? Bagaimana mereka bisa lulus jika Anda tidak memberikannya kepada mereka?
Vito De Tullio
btw, dapatkah Anda memberikan contoh kebutuhan Anda akan fungsi anonim?
Vito De Tullio
1
Ya, saya menemukan batasan satu ekspresi benar-benar membuat frustrasi. Benar, bahwa jika mereka mengizinkan lambda multi-ekspresi orang pasti akan mulai menyalahgunakannya tetapi sebaliknya omho terlalu membatasi.
rbaleksandar
10

Saya tahu ini sangat tua, tetapi menempatkan di sini sebagai referensi.

Alternatif untuk menggunakan lambda bisa dengan menggunakan defcara non-konvensional. Tujuannya adalah untuk meneruskan deffungsi, yang dapat dilakukan hanya dalam satu keadaan - dekorator. Perhatikan bahwa dengan implementasi def resultini tidak membuat fungsi, itu menciptakan hasil reduce(), yang akhirnya menjadi a dict.

Steker tak tahu malu : Saya melakukan banyak hal di sini .

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

Perhatikan bahwa multi-pernyataan lambdas dapat dilakukan, tetapi hanya dengan kode yang benar-benar jelek. Namun, yang menarik adalah bagaimana pelingkupan bekerja dengan implementasi ini (perhatikan penggunaan beragam namevariabel dan bayangan messagevariabel.

>>> 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!
pyrospade
sumber
+1 untuk pendekatan monadik
jozefg
Monads juga disebut thenables atau future / janji atau bahkan callback dalam JavaScript BTW.
aoeu256
3

Meretas bersama-sama lambda multi-pernyataan tidak seburuk yang dibuat oleh pyrospade: yakin kita bisa membuat banyak fungsi monadik menggunakan bind, seperti di Haskell, tapi karena kita berada di dunia Python yang tidak murni, kita juga bisa gunakan efek samping untuk mencapai hal yang sama.

Saya membahas beberapa cara untuk melakukan ini di blog saya .

Sebagai contoh, Python menjamin untuk mengevaluasi elemen tuple secara berurutan, sehingga kita dapat menggunakan ,banyak seperti perintah ;. Kami dapat mengganti banyak pernyataan, seperti print, dengan ekspresi, seperti sys.stdout.write.

Karenanya berikut ini setara:

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]

Perhatikan bahwa saya telah menambahkan Nonedi bagian akhir, dan mengekstraknya menggunakan [-1]; ini menetapkan nilai kembali secara eksplisit. Kita tidak harus melakukan ini, tetapi tanpanya kita akan mendapatkan (None, None, None)nilai pengembalian yang funky , yang mungkin atau mungkin tidak kita pedulikan.

Jadi kita bisa mengurutkan tindakan IO. Bagaimana dengan variabel lokal?

Python =membentuk pernyataan, jadi kita perlu menemukan ekspresi yang setara. Salah satu caranya adalah dengan memutasi konten struktur data, diteruskan sebagai argumen. Sebagai contoh:

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])({})

Ada beberapa trik yang digunakan di stateful_lambda:

  • The *_argumen memungkinkan lambda kami untuk mengambil setiap jumlah argumen. Karena ini memungkinkan nol argumen, kami memulihkan konvensi pemanggilan stateful_def.
    • Memanggil argumen _hanyalah konvensi yang mengatakan "Saya tidak akan menggunakan variabel ini"
  • Kami memiliki satu ("pembungkus") fungsi mengembalikan fungsi ("utama") yang lain: lambda state: lambda *_: ...
    • Berkat lingkup leksikal , argumen fungsi pertama akan berada di dalam lingkup untuk fungsi kedua
    • Menerima beberapa argumen sekarang dan mengembalikan fungsi lain untuk menerima sisanya kemudian dikenal sebagai currying
  • Kami segera memanggil fungsi "pembungkus", meneruskannya dengan kamus kosong: (lambda state: ...)({})
    • Ini memungkinkan kita menetapkan variabel stateke nilai {}tanpa menggunakan pernyataan penugasan (mis. state = {})
  • Kami memperlakukan kunci dan nilai statesebagai nama variabel dan nilai terikat
    • Ini kurang praktis daripada menggunakan lambda yang langsung disebut
    • Ini memungkinkan kita untuk mengubah nilai variabel
    • Kami menggunakan state.setdefault(a, b)bukannya a = bdan state.get(a)bukannyaa
  • Kami menggunakan tuple untuk menyatukan efek samping kami, seperti sebelumnya
  • Kami gunakan [-1]untuk mengekstrak nilai terakhir, yang bertindak seperti returnpernyataan

Tentu saja ini cukup rumit, tetapi kita bisa membuat API yang lebih bagus dengan fungsi pembantu:

# 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
sumber
apakah Anda bercanda, ini seperti lol mimpi buruk
Alexander Mills
1
@AlexanderMills Heh, ini tidak dimaksudkan sebagai contoh dunia nyata, lebih merupakan sanggahan dari pendekatan lambdas-in-lambdas-in-lambdas dari pyrospade, untuk menunjukkan bahwa segala sesuatunya tidak terlalu buruk. Faktanya, ini dapat disederhanakan lebih jauh sekarang karena kita memiliki python.org/dev/peps/pep-0572
Warbo
1

Saya pikir saya bisa berkontribusi, gunakan line breaker:

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

Jangan lupa hal yang sangat bagus yang bisa ditulis oleh python pada orang-orang yang tidak bertanggung jawab, seperti dalam contoh:

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

Dan lambda sangat kuat, tetapi itu tidak dimaksudkan untuk menggantikan seluruh fungsi, maksud saya Anda dapat meretasnya seperti (meminjam contoh dari kolega di atas):

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)

Tetapi apakah Anda benar-benar ingin melakukannya seperti ini? Ini sebagian besar tidak dapat dibaca setelah beberapa waktu, itu seperti sampai ke awal tali dimulai dengan ujung yang tidak terurai. tali terurai

Lambdas disebut sebagai fungsi satu-satunya, dalam peta, memfilter dan mengurangi fungsi dalam Pemrograman Berorientasi Fungsional (antara lain). Misalnya mendapatkan nilai karakter dari nilai yang integer dan habis dibagi 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]

Anda bisa menggunakannya sebagai fungsi ekspresi fungsi dengan mendifinisikan fungsi kompleks (atau lebih dan beberapa lambda, dan meletakkannya di dalam lambda lain:

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

tetapi Python memiliki dukungan fungsi expresi dengan cara lain: -lets mengatakan Anda memiliki beberapa fungsi yang dipanggil superAwesomeFunctiondan fungsi itu dapat melakukan beberapa hal yang sangat mengagumkan, Anda dapat menetapkannya ke variabel dengan tidak memanggilnya, seperti ini:

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

Jadi sekarang ketika Anda memanggil SAF Anda akan memanggil superAwesomeFunction atau metode. Jika Anda mencari melalui folder Lib Anda, Anda dapat menemukan bahwa sebagian besar __builtin__modul python ditulis seperti itu. Ini dilakukan karena kadang-kadang Anda akan memerlukan beberapa fungsi yang melakukan tugas tertentu yang tidak cukup untuk dapat digunakan oleh pengguna tetapi diperlukan untuk beberapa fungsi. Jadi Anda memiliki pilihan, Anda tidak dapat memiliki 2 fungsi dengan nama "superAwesomeFunction", Anda dapat memiliki "superAwesomeFunctionDoingBasicStuf" dan "realSuperAwesomeFunction" dan daripada hanya meletakkan "realSuperAwesomeFunction" pada variabel "superAwesomeFunction" dan Anda selesai.

Anda dapat menemukan lokasi modul yang diimpor dengan memasukkan di konsol importedModule.__file__(contoh nyata import os;os.__file__), dan cukup ikuti direktori itu ke file yang disebut importModule.py dan buka di editor dan temukan bagaimana Anda dapat memaksimalkan "pengetahuan" Anda sendiri.

Saya harap ini membantu Anda dan mungkin rekan kerja lain yang bermasalah.

Danilo
sumber