Tidak ada Multiline Lambda di Python: Kenapa tidak?

335

Saya pernah mendengar bahwa multiline lambdas tidak dapat ditambahkan dengan Python karena mereka akan berbenturan secara sintaksis dengan konstruksi sintaks lainnya dalam Python. Saya sedang memikirkan hal ini di bus hari ini dan menyadari bahwa saya tidak dapat memikirkan konstruksi Python tunggal yang bertentangan dengan lambdas multiline. Karena saya tahu bahasanya dengan baik, ini mengejutkan saya.

Sekarang, saya yakin Guido punya alasan untuk tidak memasukkan lambda multiline ke dalam bahasa itu, tetapi karena penasaran: bagaimana situasi di mana termasuk lambda multiline akan ambigu? Apakah yang saya dengar benar, atau adakah alasan lain mengapa Python tidak mengizinkan lambda multiline?

Imagist
sumber
12
tl; dr version: karena Python adalah bahasa yang malas tanpa {} blok dan karenanya ini tidak diizinkan untuk menjaga desain sintaksis yang konsisten.
Andrew
11
Juga: Saya benar-benar terkejut tidak ada yang menyebutkan ini dalam jawaban ... Anda dapat mengakhiri baris dengan \ karakter dalam Python dan melanjutkan ke baris berikutnya ... Informasi ini agak menggantikan seluruh pertanyaan ini jadi ...
Andrew
"desain sintaksis"
nicolas
Ini akan membutuhkan pernyataan yang diizinkan di dalam ekspresi. Jika Anda akan melakukan itu, Anda tidak perlu lambdaekspresi terlebih dahulu; Anda cukup menggunakan defpernyataan dalam ekspresi.
chepner

Jawaban:

153

Lihatlah yang berikut ini:

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

Apakah ini lambda yang kembali (y, [1,2,3])(sehingga peta hanya mendapatkan satu parameter, menghasilkan kesalahan)? Atau apakah itu kembali y? Atau apakah itu kesalahan sintaksis, karena koma pada baris baru salah tempat? Bagaimana Python tahu apa yang Anda inginkan?

Di dalam parens, lekukan tidak menjadi masalah bagi python, jadi Anda tidak dapat bekerja dengan multinines.

Ini hanya yang sederhana, mungkin ada lebih banyak contoh.

balpha
sumber
107
mereka bisa memaksakan penggunaan tanda kurung jika Anda ingin mengembalikan tupel dari lambda. IMO, ini seharusnya selalu ditegakkan untuk mencegah ambiguitas seperti itu, tapi oh well.
mpen
26
Ini adalah ambiguitas sederhana yang harus diselesaikan dengan menambahkan seperangkat parens tambahan, sesuatu yang sudah ada di banyak tempat, misalnya ekspresi generator yang dikelilingi oleh argumen lain, memanggil metode pada integer literal (walaupun ini tidak harus menjadi kasus sejak suatu nama fungsi tidak dapat dimulai dengan digit), dan tentu saja lambda baris tunggal (yang mungkin merupakan ekspresi panjang yang ditulis pada banyak baris). Lambda multi-line tidak akan jauh berbeda dari kasus-kasus ini yang menjamin pengecualian mereka atas dasar itu. Inilah jawaban sebenarnya.
nmclean
3
Saya suka bagaimana ada trilyun bahasa yang menghadapinya tanpa rasa khawatir, tetapi entah bagaimana ada beberapa alasan mendalam mengapa ini seharusnya sangat sulit jika bukan tidak mungkin
nicolas
1
@nicolas singkatnya python
javadba
Alasan mengapa saya tidak menggunakan lambda, jadi kurang dikembangkan dengan Python.
NoName
635

Guido van Rossum (penemu Python) menjawab pertanyaan persis ini sendiri di sebuah posting blog lama .
Pada dasarnya, ia mengakui bahwa secara teori itu mungkin, tetapi bahwa solusi apa pun yang diajukan akan tidak Pythonic:

"Tetapi kompleksitas dari setiap solusi yang diusulkan untuk teka-teki ini sangat besar, bagi saya: itu membutuhkan parser (atau lebih tepatnya, lexer) untuk dapat beralih bolak-balik antara mode indent-sensitive dan indent-insensitive, menjaga setumpuk mode sebelumnya dan level indentasi. Secara teknis itu semua bisa diselesaikan (sudah ada setumpuk level indentasi yang bisa digeneralisasi). Tapi tidak ada yang menghilangkan perasaan saya bahwa itu semua adalah alat Rube Goldberg yang rumit . "

Eli Courtwright
sumber
108
Mengapa ini bukan jawaban teratas? Ini bukan tentang alasan teknis, ini adalah pilihan desain, sebagaimana dinyatakan dengan jelas oleh penemunya.
Dan Abramov
13
@DanAbramov karena OP tidak masuk selama bertahun-tahun mungkin.
Kontrak Prof. Falken dilanggar
7
Bagi mereka yang tidak mengerti referensi Rube Goldberg, lihat: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj
56
Jawaban Guido hanyalah alasan lain saya berharap Python tidak bergantung pada indentasi untuk mendefinisikan blok.
LS
25
Saya tidak yakin saya akan menyebut "firasat" pilihan desain. ;)
Elliot Cameron
54

Ini umumnya sangat jelek (tapi terkadang alternatifnya bahkan lebih jelek), jadi solusinya adalah dengan membuat ekspresi kawat gigi:

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

Itu tidak akan menerima tugas apa pun, jadi Anda harus menyiapkan data terlebih dahulu. Tempat saya menemukan ini berguna adalah pembungkus PySide, di mana Anda kadang-kadang memiliki panggilan balik singkat. Menulis fungsi anggota tambahan akan lebih jelek. Biasanya Anda tidak membutuhkan ini.

Contoh:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Sebastian Bartos
sumber
2
Bos saya hanya meminta sesuatu seperti ini di aplikasi PyQt kami. Luar biasa!
TheGerm
1
Terima kasih untuk ini, saya juga mencari cara yang baik untuk menggunakan lambda pendek (tapi masih multiline) sebagai panggilan balik untuk UI PySide kami.
Michael Leonard
Dan sekarang saya telah melihat ini langsung disarankan menggunakan lambda argdan setattr(arg, 'attr','value')menumbangkan "tidak ada tugas ...". Dan kemudian ada evaluasi hubung singkat anddan or... Javascript yang melakukannya. Tenggelamkan akar ke dalam dirimu, seperti ivy ke dinding. Saya hampir berharap saya melupakan ini selama Natal.
nigel222
cukup pintar - dan cukup mudah dibaca. Sekarang - tentang tugas-tugas itu (hilang ..) ..
javadba
@ nigel222 mengapa merasa malu? Bahasa python secara fundamental lumpuh - tapi itu yang digunakan lagian untuk banyak ilmu Data . Jadi kami melakukan penyesuaian. Menemukan cara untuk melakukan efek samping (cukup sering hanya mencetak / masuk!) Dan tugas (sering cukup hanya untuk vars menengah!) Harus ditangani dengan baik oleh bahasa. Tetapi mereka bahkan tidak didukung (setidaknya jika Anda mengikuti PEPpedoman)
javadba
17

Beberapa tautan yang relevan:

Untuk sementara, saya mengikuti pengembangan Reia, yang awalnya akan memiliki sintaksis Python berdasarkan lekukan dengan blok Ruby juga, semua di atas Erlang. Tapi, sang desainer akhirnya menyerah pada sensitivitas lekukan, dan posting ini ia menulis tentang keputusan itu termasuk diskusi tentang masalah yang ia hadapi dengan lekukan + blok multi-line, dan peningkatan apresiasi yang ia dapatkan untuk masalah / keputusan desain Guido:

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

Juga, berikut ini adalah proposal yang menarik untuk blok gaya-Ruby di Python yang saya jalankan di mana Guido memposting respons tanpa benar-benar menembaknya (meskipun tidak yakin apakah telah terjadi penurunan berikutnya):

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

Segera
sumber
12

[Sunting] Baca jawaban ini.Ini menjelaskan mengapa multiline lambda bukanlah suatu hal.

Sederhananya, itu unpythonic. Dari posting blog Guido van Rossum:

Saya menemukan solusi apa pun yang tidak dapat diterima yang menanamkan blok berbasis indentasi di tengah ekspresi. Karena saya menemukan sintaks alternatif untuk pengelompokan pernyataan (mis. Kurung kurawal atau kata kunci mulai / akhir) sama-sama tidak dapat diterima, ini membuat lambda multi-baris menjadi teka-teki yang tidak dapat diselesaikan.

Samy Bencherif
sumber
10

Izinkan saya mempersembahkan kepada Anda hack yang mulia tetapi menakutkan:

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)

Anda sekarang dapat menggunakan LETformulir ini sebagai berikut:

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

pemberian yang mana: [0, 3, 8]

divs1210
sumber
Ini luar biasa! Saya pikir saya akan menggunakan ini lain kali saya menulis Python. Saya terutama seorang Lisp dan programmer JS, dan kurangnya multi baris lambada sakit. Ini cara untuk mendapatkannya.
Christopher Dumas
7

Saya bersalah mempraktikkan peretasan kotor ini di beberapa proyek saya yang sedikit lebih sederhana:

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

Saya harap Anda dapat menemukan cara untuk tetap pythonic tetapi jika Anda harus melakukannya ini tidak begitu menyakitkan daripada menggunakan exec dan memanipulasi global.

S.Rad
sumber
6

Biarkan saya mencoba untuk mengatasi masalah parsing @balpha. Saya akan menggunakan tanda kurung di sekitar lama multiline. Jika tidak ada tanda kurung, definisi lambda serakah. Jadi lambda masuk

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

mengembalikan fungsi yang mengembalikan (y*z, [1,2,3])

Tapi

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

cara

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

di mana func adalah lambda multiline yang mengembalikan y * z. Apakah itu bekerja?

Wai Yip Tung
sumber
1
Saya pikir yang paling atas harus kembali map(func, [1,2,3])dan yang paling bawah harus menjadi kesalahan karena fungsi peta tidak memiliki cukup argumen. Juga ada beberapa tanda kurung tambahan dalam kode.
Samy Bencherif
menjatuhkan itu ke dalam pycharm menjalankan python2.7.13 itu memberikan kesalahan sintaksis.
simbo1905
tanda kurung ekstra
Samy Bencherif
4

(Bagi siapa pun yang masih tertarik dengan topik tersebut.)

Pertimbangkan ini (termasuk penggunaan nilai pengembalian pernyataan dalam pernyataan lebih lanjut dalam lambda "multiline", meskipun jelek sampai muntah ;-)

>>> 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
sumber
Ini tidak berfungsi ketika dipanggil untuk kedua kalinya dengan parameter yang berbeda dan menyebabkan kebocoran memori kecuali baris pertama state.clear()karena argumen default hanya dibuat sekali ketika fungsi dibuat.
Matthew D. Scholefield
1

Anda cukup menggunakan slash ( \) jika Anda memiliki banyak baris untuk fungsi lambda Anda

Contoh:

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

Output: 30
IRSHAD
sumber
Pertanyaannya menyangkut penggunaan lebih dari 1 ekspresi daripada lebih dari 1 baris literal.
Tomas Zubiri
1

Saya mulai dengan python tetapi berasal dari Javascript cara yang paling jelas adalah mengekstrak ekspresi sebagai fungsi ....

Contoh yang dibuat, ekspresi multiply (x*2)diekstraksi sebagai fungsi dan karena itu saya bisa menggunakan multiline:

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

Mungkin itu tidak menjawab pertanyaan yang tepat jika itu adalah bagaimana melakukan multiline dalam ekspresi lambda itu sendiri , tetapi jika seseorang mendapatkan utas ini mencari cara men-debug ekspresi (seperti saya), saya pikir itu akan membantu

Vicens Fayos
sumber
2
Mengapa saya melakukan itu dan tidak sekadar menulis map(multiply, [1, 2, 3])?
thothal
0

Mengenai peretasan jelek, Anda selalu dapat menggunakan kombinasi execdan fungsi reguler untuk mendefinisikan fungsi multiline seperti ini:

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

Anda dapat membungkus ini menjadi fungsi seperti:

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
sumber
0

Saya hanya bermain sedikit untuk mencoba membuat pemahaman dict dengan mengurangi, dan muncul dengan hack liner yang satu ini:

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}

Saya hanya mencoba melakukan hal yang sama seperti apa yang dilakukan dalam pemahaman dict Javascript ini: https://stackoverflow.com/a/11068265

rodfersou
sumber
0

Inilah implementasi yang lebih menarik dari lambda multi-jalur. Tidak mungkin untuk mencapai karena bagaimana python menggunakan indentasi sebagai cara untuk menyusun kode.

Tapi untungnya bagi kami, pemformatan indent dapat dinonaktifkan menggunakan array dan tanda kurung.

Seperti yang sudah ditunjukkan beberapa orang, Anda dapat menulis kode seperti itu:

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

Secara teori jika Anda dijamin memiliki evaluasi dari kiri ke kanan itu akan berhasil tetapi Anda masih kehilangan nilai yang diteruskan dari satu ekspresi ke yang lain.

Salah satu cara untuk mencapai apa yang sedikit lebih bertele-tele adalah dengan memiliki

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

Di mana setiap lambda menerima argumen dari yang sebelumnya.

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

Metode ini memungkinkan Anda menulis sesuatu yang sedikit mirip / skema.

Jadi Anda dapat menulis hal-hal seperti ini:

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

Metode yang lebih kompleks dapat digunakan untuk menghitung sisi miring

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

Ini akan mengembalikan daftar angka skalar sehingga dapat digunakan untuk mengurangi beberapa nilai menjadi satu.

Memiliki banyak lambda tentu tidak akan menjadi sangat efisien tetapi jika Anda dibatasi itu bisa menjadi cara yang baik untuk menyelesaikan sesuatu dengan cepat maka tulis ulang sebagai fungsi yang sebenarnya nanti.

Loïc Faure-Lacroix
sumber
-3

karena fungsi lambda seharusnya satu-baris, sebagai bentuk fungsi yang paling sederhana, an entrance, then return

Sphynx-HenryAY
sumber
1
Tidak, siapa pun yang menulis program berbasis-acara akan memberi tahu Anda bahwa multiline lambda itu penting.
Akangka