Bagaimana saya bisa menulis ekspresi lambda yang setara dengan:
def x():
raise Exception()
Hal berikut ini tidak diperbolehkan:
y = lambda : raise Exception()
Bagaimana saya bisa menulis ekspresi lambda yang setara dengan:
def x():
raise Exception()
Hal berikut ini tidak diperbolehkan:
y = lambda : raise Exception()
y=lambda...over def y:itu?
Jawaban:
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:
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.
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/0adalah solusi super cerdas jika jenis Pengecualian tidak relevan
Bagaimana tentang:
lambda x: exec('raise(Exception(x))')
SyntaxErrorPython 2.7.11.
Fungsi yang dibuat dengan formulir lambda tidak boleh berisi pernyataan .
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.
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, gunakan lambda : [][0]; untuk dua, gunakan lambda 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())
lambda: 0adalah turunan dari builtins.functionkelas.
type(lambda: 0)adalah builtins.functionkelasnya.
(lambda: 0).__code__adalah sebuah codeobjek.
Sebuah codeobjek 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 untuk flagsargumen sebagai contoh CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.
Argumen yang paling penting adalah codestringyang 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.
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.
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
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.