Jadi Anda tidak bisa melakukan itu. Gunakan fungsi normal.
DrTyrsa
1
Apa gunanya memberi nama pada fungsi anonim?
John La Rooy
2
@gnibbler Anda dapat menggunakan nama untuk merujuk ke fungsi tersebut. y () lebih mudah digunakan daripada (lambda: 0) () di REPL.
Thomas Jung
Jadi apa keuntungan dari y=lambda...over def y:itu?
John La Rooy
@gnibbler Beberapa konteks: Saya ingin mendefinisikan fungsi def g (f, e) yang memanggil f dalam happy case dan e jika kesalahan terdeteksi. Bergantung pada skenario, e dapat memunculkan pengecualian atau mengembalikan beberapa nilai yang valid. Untuk menggunakan g saya ingin menulis g (lambda x: x * 2, lambda e: raise e) atau alternatif g (lambda x: x * 2, lambda e: 0).
Thomas Jung
Jawaban:
173
Ada lebih dari satu cara untuk menguliti Python:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Lambdas menerima pernyataan. Karena raise exmerupakan pernyataan, Anda dapat menulis penggalang tujuan umum:
defraise_(ex):raise ex
y = lambda: raise_(Exception('foobar'))
Tetapi jika tujuan Anda adalah untuk menghindari a def, ini jelas tidak cukup. Itu memang, namun memungkinkan Anda untuk meningkatkan pengecualian secara bersyarat, misalnya:
y = lambda x: 2*x if x < 10else raise_(Exception('foobar'))
Alternatifnya Anda bisa memunculkan pengecualian tanpa menentukan fungsi bernama. Yang Anda butuhkan hanyalah perut yang kuat (dan 2.x untuk kode yang diberikan):
Jika Anda tidak peduli apa jenis eksepsi dilemparkan, berikut juga bekerja: lambda: 1 / 0. Anda hanya akan berakhir dengan ZeroDivisionError yang dilemparkan, bukan pengecualian biasa. Ingatlah bahwa jika pengecualian diizinkan untuk menyebar, mungkin terlihat aneh bagi seseorang yang men-debug kode Anda untuk mulai melihat sekelompok ZeroDivisionErrors.
Warren Spencer
Solusi hebat @WarrenSpencer. Sebagian besar kode tidak memiliki banyak kesalahan pembagian nol, jadi sangat berbeda jika Anda dapat memilih jenisnya sendiri.
jwg
2
y = 1/0adalah solusi super cerdas jika jenis Pengecualian tidak relevan
Saher Ahwal
5
Adakah yang bisa memberi tahu kami tentang apa yang sebenarnya terjadi dalam solusi 'seni gelap / perut kuat'?
Ini benar, tetapi tidak benar-benar menjawab pertanyaan itu; meningkatkan pengecualian dari ekspresi lambda mudah dilakukan (dan menangkapnya terkadang dapat dilakukan dengan menggunakan trik yang dibuat-buat, lihat stackoverflow.com/a/50916686/2560053 atau stackoverflow.com/a/50961836/2560053 ).
Thomas Baruchel
16
Sebenarnya ada caranya, tapi sangat dibuat-buat.
Anda dapat membuat objek kode menggunakan compile()fungsi bawaan. Ini memungkinkan Anda untuk menggunakan raisepernyataan (atau pernyataan lain, dalam hal ini), tetapi ini menimbulkan tantangan lain: mengeksekusi objek kode. Cara biasa adalah dengan menggunakan execpernyataan, tetapi itu membawa Anda kembali ke masalah aslinya, yaitu Anda tidak dapat mengeksekusi pernyataan dalam lambda(atau eval(), dalam hal ini).
Solusinya adalah hack. Callable seperti hasil dari sebuah lambdapernyataan semuanya memiliki atribut __code__, yang sebenarnya dapat diganti. Jadi, jika Anda membuat callable dan mengganti __code__nilainya dengan objek kode dari atas, Anda mendapatkan sesuatu yang dapat dievaluasi tanpa menggunakan pernyataan. Namun, pencapaian semua ini menghasilkan kode yang sangat tidak jelas:
map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
Di atas melakukan hal berikut:
yang compile()panggilan membuat objek kode yang menimbulkan pengecualian;
yang lambda: 0kembali sebuah callable yang tidak hanya mengembalikan nilai 0 - ini digunakan untuk mengeksekusi obyek kode di atas kemudian;
yang lambda x, y, zmembuat fungsi yang memanggil __setattr__metode argumen pertama dengan argumen yang tersisa, DAN MENGEMBALIKAN ARGUMEN PERTAMA! Ini perlu, karena dengan __setattr__sendirinya kembali None;
yang map()panggilan mengambil hasil lambda: 0, dan menggunakan lambda x, y, zmenggantikannya ini __code__objek dengan hasil dari compile()panggilan. Hasil dari operasi peta ini adalah daftar dengan satu entri, yang dikembalikan oleh lambda x, y, z, itulah mengapa kita membutuhkan ini lambda: jika kita akan __setattr__langsung menggunakannya , kita akan kehilangan referensi ke lambda: 0objek!
akhirnya, elemen pertama (dan satu-satunya) dari daftar yang dikembalikan oleh map()panggilan tersebut dijalankan, menghasilkan objek kode yang dipanggil, yang pada akhirnya meningkatkan pengecualian yang diinginkan.
Ini berfungsi (diuji dengan Python 2.6), tetapi jelas tidak cantik.
Satu catatan terakhir: jika Anda memiliki akses ke typesmodul (yang akan membutuhkan untuk menggunakan importpernyataan sebelum Anda eval), maka Anda dapat mempersingkat kode ini sedikit: menggunakan types.FunctionType()Anda dapat membuat fungsi yang akan mengeksekusi objek kode yang diberikan, jadi Anda menang tidak perlu retasan untuk membuat fungsi dummy dengan lambda: 0dan mengganti nilai __code__atributnya.
Jika yang Anda inginkan hanyalah ekspresi lambda yang memunculkan pengecualian sewenang-wenang, Anda dapat melakukannya dengan ekspresi ilegal. Misalnya, lambda x: [][0]akan mencoba mengakses elemen pertama dalam daftar kosong, yang akan memunculkan IndexError.
HARAP DICATAT : Ini adalah hack, bukan fitur. Jangan gunakan ini dalam kode (non-kode-golf) yang mungkin dilihat atau digunakan oleh orang lain.
Dalam kasus saya saya mendapatkan: TypeError: <lambda>() takes exactly 1 positional argument (2 given). Apakah Anda yakin dengan IndexError?
Jovik
4
Ya. Apakah Anda mungkin memberikan jumlah argumen yang salah? Jika Anda membutuhkan fungsi lambda yang dapat menerima sejumlah argumen, gunakan lambda *x: [][0]. (Versi asli hanya membutuhkan satu argumen; tanpa argumen, gunakan lambda : [][0]; untuk dua, gunakan lambda x,y: [][0]; dll.)
Kyle Strand
4
Saya telah mengembangkan ini sedikit: lambda x: {}["I want to show this message. Called with: %s" % x] Menghasilkan: KeyError: 'I want to show this message. Called with: foo'
ErlVolton
@ErlVolPintar! Meskipun menggunakan ini di mana saja kecuali dalam skrip satu kali sepertinya ide yang buruk ...
Kyle Strand
Saya menggunakan sementara dalam pengujian unit untuk proyek di mana saya tidak repot-repot membuat tiruan nyata dari logger saya. Ini muncul jika Anda mencoba untuk mencatat kesalahan atau kritis. Jadi ... Ya mengerikan, meskipun suka sama suka :)
ErlVolton
14
Saya ingin memberikan penjelasan UPDATE 3 dari jawaban yang diberikan oleh Marcelo Cantos:
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
Jadi kami mendorong referensi ke xke tumpukan. Ini varnamesadalah daftar string yang hanya berisi 'x'. Kami akan mendorong satu-satunya argumen dari fungsi yang kami definisikan ke tumpukan.
Byte berikutnya adalah opcode untuk RAISE_VARARGSdan byte berikutnya adalah argumennya yaitu 1.
RAISE_VARARGS(argc)
Raises an exception using one of the 3 forms of the raise statement, depending on the value of argc:
0: raise (re-raise previous exception)
1: raise TOS (raise exception instance or type at TOS)
2: raise TOS1 from TOS (raise exception instance or type at TOS1 with __cause__ set to TOS)
TOS adalah top-of-stack. Karena kita mendorong argumen pertama ( x) dari fungsi kita ke tumpukan dan argcbernilai 1, kita akan menaikkan
xjika itu adalah contoh pengecualian atau membuat contoh xdan menaikkannya sebaliknya.
Byte terakhir yaitu 0 tidak digunakan. Ini bukan opcode yang valid. Mungkin juga tidak ada di sana.
Kami kemudian memanggil fungsi yang dibangun dengan meneruskan instance Exception sebagai argumen. Akibatnya kami memanggil fungsi lambda yang memunculkan pengecualian. Mari kita jalankan cuplikannya dan lihat apakah itu berfungsi sebagaimana mestinya.
>>> type(lambda: 0)(type((lambda: 0).__code__)(
... 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
... )(Exception())
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
File "", line 1, in
Exception
Perbaikan
Kami melihat bahwa byte terakhir dari bytecode tidak berguna. Jangan mengacaukan ekspresi rumit ini dengan sia-sia. Mari hapus byte itu. Juga jika kita ingin bermain golf sedikit, kita bisa menghilangkan instansiasi Exception dan sebagai gantinya meneruskan kelas Exception sebagai argumen. Perubahan tersebut akan menghasilkan kode berikut:
y=lambda...
overdef y:
itu?Jawaban:
Ada lebih dari satu cara untuk menguliti Python:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Lambdas menerima pernyataan. Karena
raise ex
merupakan pernyataan, Anda dapat menulis penggalang tujuan umum:def raise_(ex): raise ex y = lambda: raise_(Exception('foobar'))
Tetapi jika tujuan Anda adalah untuk menghindari a
def
, ini jelas tidak cukup. Itu memang, namun memungkinkan Anda untuk meningkatkan pengecualian secara bersyarat, misalnya:y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))
Alternatifnya Anda bisa memunculkan pengecualian tanpa menentukan fungsi bernama. Yang Anda butuhkan hanyalah perut yang kuat (dan 2.x untuk kode yang diberikan):
type(lambda:0)(type((lambda:0).func_code)( 1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{} )(Exception())
Dan solusi perut kuat python3 :
type(lambda: 0)(type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{} )(Exception())
Terima kasih @WarrenSpencer untuk menunjukkan jawaban yang sangat sederhana jika Anda tidak peduli yang eksepsi dimunculkan:
y = lambda: 1/0
.sumber
lambda: 1 / 0
. Anda hanya akan berakhir dengan ZeroDivisionError yang dilemparkan, bukan pengecualian biasa. Ingatlah bahwa jika pengecualian diizinkan untuk menyebar, mungkin terlihat aneh bagi seseorang yang men-debug kode Anda untuk mulai melihat sekelompok ZeroDivisionErrors.y = 1/0
adalah solusi super cerdas jika jenis Pengecualian tidak relevanBagaimana tentang:
lambda x: exec('raise(Exception(x))')
sumber
SyntaxError
Python 2.7.11.Fungsi yang dibuat dengan formulir lambda tidak boleh berisi pernyataan .
sumber
Sebenarnya ada caranya, tapi sangat dibuat-buat.
Anda dapat membuat objek kode menggunakan
compile()
fungsi bawaan. Ini memungkinkan Anda untuk menggunakanraise
pernyataan (atau pernyataan lain, dalam hal ini), tetapi ini menimbulkan tantangan lain: mengeksekusi objek kode. Cara biasa adalah dengan menggunakanexec
pernyataan, tetapi itu membawa Anda kembali ke masalah aslinya, yaitu Anda tidak dapat mengeksekusi pernyataan dalamlambda
(ataueval()
, dalam hal ini).Solusinya adalah hack. Callable seperti hasil dari sebuah
lambda
pernyataan semuanya memiliki atribut__code__
, yang sebenarnya dapat diganti. Jadi, jika Anda membuat callable dan mengganti__code__
nilainya dengan objek kode dari atas, Anda mendapatkan sesuatu yang dapat dievaluasi tanpa menggunakan pernyataan. Namun, pencapaian semua ini menghasilkan kode yang sangat tidak jelas:map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
Di atas melakukan hal berikut:
yang
compile()
panggilan membuat objek kode yang menimbulkan pengecualian;yang
lambda: 0
kembali sebuah callable yang tidak hanya mengembalikan nilai 0 - ini digunakan untuk mengeksekusi obyek kode di atas kemudian;yang
lambda x, y, z
membuat fungsi yang memanggil__setattr__
metode argumen pertama dengan argumen yang tersisa, DAN MENGEMBALIKAN ARGUMEN PERTAMA! Ini perlu, karena dengan__setattr__
sendirinya kembaliNone
;yang
map()
panggilan mengambil hasillambda: 0
, dan menggunakanlambda x, y, z
menggantikannya ini__code__
objek dengan hasil daricompile()
panggilan. Hasil dari operasi peta ini adalah daftar dengan satu entri, yang dikembalikan olehlambda x, y, z
, itulah mengapa kita membutuhkan inilambda
: jika kita akan__setattr__
langsung menggunakannya , kita akan kehilangan referensi kelambda: 0
objek!akhirnya, elemen pertama (dan satu-satunya) dari daftar yang dikembalikan oleh
map()
panggilan tersebut dijalankan, menghasilkan objek kode yang dipanggil, yang pada akhirnya meningkatkan pengecualian yang diinginkan.Ini berfungsi (diuji dengan Python 2.6), tetapi jelas tidak cantik.
Satu catatan terakhir: jika Anda memiliki akses ke
types
modul (yang akan membutuhkan untuk menggunakanimport
pernyataan sebelum Andaeval
), maka Anda dapat mempersingkat kode ini sedikit: menggunakantypes.FunctionType()
Anda dapat membuat fungsi yang akan mengeksekusi objek kode yang diberikan, jadi Anda menang tidak perlu retasan untuk membuat fungsi dummy denganlambda: 0
dan mengganti nilai__code__
atributnya.sumber
Jika yang Anda inginkan hanyalah ekspresi lambda yang memunculkan pengecualian sewenang-wenang, Anda dapat melakukannya dengan ekspresi ilegal. Misalnya,
lambda x: [][0]
akan mencoba mengakses elemen pertama dalam daftar kosong, yang akan memunculkan IndexError.HARAP DICATAT : Ini adalah hack, bukan fitur. Jangan gunakan ini dalam kode (non-kode-golf) yang mungkin dilihat atau digunakan oleh orang lain.
sumber
TypeError: <lambda>() takes exactly 1 positional argument (2 given)
. Apakah Anda yakin dengan IndexError?lambda *x: [][0]
. (Versi asli hanya membutuhkan satu argumen; tanpa argumen, gunakanlambda : [][0]
; untuk dua, gunakanlambda x,y: [][0]
; dll.)lambda x: {}["I want to show this message. Called with: %s" % x]
Menghasilkan:KeyError: 'I want to show this message. Called with: foo'
Saya ingin memberikan penjelasan UPDATE 3 dari jawaban yang diberikan oleh Marcelo Cantos:
type(lambda: 0)(type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{} )(Exception())
Penjelasan
lambda: 0
adalah turunan daribuiltins.function
kelas.type(lambda: 0)
adalahbuiltins.function
kelasnya.(lambda: 0).__code__
adalah sebuahcode
objek.Sebuah
code
objek adalah obyek yang memegang bytecode disusun antara lain. Ini didefinisikan di sini di CPython https://github.com/python/cpython/blob/master/Include/code.h . Metodenya diterapkan di sini https://github.com/python/cpython/blob/master/Objects/codeobject.c . Kami dapat menjalankan bantuan pada objek kode:Help on code object: class code(object) | code(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring, | constants, names, varnames, filename, name, firstlineno, | lnotab[, freevars[, cellvars]]) | | Create a code object. Not for the faint of heart.
type((lambda: 0).__code__)
adalah kelas kode.Jadi saat kita bilang
type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
kami memanggil konstruktor objek kode dengan argumen berikut:
Anda dapat membaca tentang apa arti argumen dalam definisi
PyCodeObject
https://github.com/python/cpython/blob/master/Include/code.h . Nilai 67 untukflags
argumen sebagai contohCO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE
.Argumen yang paling penting adalah
codestring
yang berisi opcode instruksi. Mari kita lihat apa artinya.>>> import dis >>> dis.dis(b'|\0\202\1\0') 0 LOAD_FAST 0 (0) 2 RAISE_VARARGS 1 4 <0>
Dokumentasi opcode dapat ditemukan di sini https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions . Byte pertama adalah opcode untuk
LOAD_FAST
, byte kedua adalah argumennya yaitu 0.Jadi kami mendorong referensi ke
x
ke tumpukan. Inivarnames
adalah daftar string yang hanya berisi 'x'. Kami akan mendorong satu-satunya argumen dari fungsi yang kami definisikan ke tumpukan.Byte berikutnya adalah opcode untuk
RAISE_VARARGS
dan byte berikutnya adalah argumennya yaitu 1.RAISE_VARARGS(argc) Raises an exception using one of the 3 forms of the raise statement, depending on the value of argc: 0: raise (re-raise previous exception) 1: raise TOS (raise exception instance or type at TOS) 2: raise TOS1 from TOS (raise exception instance or type at TOS1 with __cause__ set to TOS)
TOS adalah top-of-stack. Karena kita mendorong argumen pertama (
x
) dari fungsi kita ke tumpukan danargc
bernilai 1, kita akan menaikkanx
jika itu adalah contoh pengecualian atau membuat contohx
dan menaikkannya sebaliknya.Byte terakhir yaitu 0 tidak digunakan. Ini bukan opcode yang valid. Mungkin juga tidak ada di sana.
Kembali ke cuplikan kode yang kami analisis:
type(lambda: 0)(type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{} )(Exception())
Kami memanggil konstruktor objek kode:
type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
Kami meneruskan objek kode dan kamus kosong ke konstruktor objek fungsi:
type(lambda: 0)(type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{} )
Mari panggil bantuan pada objek fungsi untuk melihat apa arti argumen tersebut.
Help on class function in module builtins: class function(object) | function(code, globals, name=None, argdefs=None, closure=None) | | Create a function object. | | code | a code object | globals | the globals dictionary | name | a string that overrides the name from the code object | argdefs | a tuple that specifies the default argument values | closure | a tuple that supplies the bindings for free variables
Kami kemudian memanggil fungsi yang dibangun dengan meneruskan instance Exception sebagai argumen. Akibatnya kami memanggil fungsi lambda yang memunculkan pengecualian. Mari kita jalankan cuplikannya dan lihat apakah itu berfungsi sebagaimana mestinya.
>>> type(lambda: 0)(type((lambda: 0).__code__)( ... 1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{} ... )(Exception()) Traceback (most recent call last): File "<stdin>", line 3, in <module> File "", line 1, in Exception
Perbaikan
Kami melihat bahwa byte terakhir dari bytecode tidak berguna. Jangan mengacaukan ekspresi rumit ini dengan sia-sia. Mari hapus byte itu. Juga jika kita ingin bermain golf sedikit, kita bisa menghilangkan instansiasi Exception dan sebagai gantinya meneruskan kelas Exception sebagai argumen. Perubahan tersebut akan menghasilkan kode berikut:
type(lambda: 0)(type((lambda: 0).__code__)( 1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{} )(Exception)
Ketika kita menjalankannya kita akan mendapatkan hasil yang sama seperti sebelumnya. Itu hanya lebih pendek.
sumber