ฉันจะเขียนนิพจน์แลมบ์ดาที่เทียบเท่ากับ:
def x():
raise Exception()
ไม่อนุญาตสิ่งต่อไปนี้:
y = lambda : raise Exception()
ฉันจะเขียนนิพจน์แลมบ์ดาที่เทียบเท่ากับ:
def x():
raise Exception()
ไม่อนุญาตสิ่งต่อไปนี้:
y = lambda : raise Exception()
y=lambda...มากกว่าdef y:นั้น?
คำตอบ:
มีหลายวิธีในการสกิน Python:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Lambdas ยอมรับงบ เนื่องจากraise exเป็นคำสั่งคุณสามารถเขียน raiser วัตถุประสงค์ทั่วไป:
def raise_(ex):
raise ex
y = lambda: raise_(Exception('foobar'))
แต่ถ้าเป้าหมายของคุณคือหลีกเลี่ยง a สิ่งdefนี้ก็ไม่ได้ตัดทิ้ง อย่างไรก็ตามอนุญาตให้คุณเพิ่มข้อยกเว้นตามเงื่อนไขเช่น:
y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))
หรือคุณสามารถเพิ่มข้อยกเว้นได้โดยไม่ต้องกำหนดชื่อฟังก์ชัน สิ่งที่คุณต้องมีคือกระเพาะอาหารที่แข็งแรง (และ 2.x สำหรับรหัสที่ระบุ):
type(lambda:0)(type((lambda:0).func_code)(
1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())
และวิธีแก้ท้องแข็ง python3 :
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
ขอบคุณ @WarrenSpencer ที่ชี้ให้เห็นคำตอบที่ง่ายมากหากคุณไม่สนใจว่าจะมีข้อยกเว้นใดy = lambda: 1/0บ้าง:
lambda: 1 / 0หากคุณไม่สนใจสิ่งที่ประเภทของการยกเว้นจะโยนต่อไปยังการทำงาน: คุณจะจบลงด้วย ZeroDivisionError ที่ถูกโยนแทนที่จะเป็นข้อยกเว้นปกติ โปรดทราบว่าหากข้อยกเว้นได้รับอนุญาตให้โพรโทเกตอาจดูแปลกสำหรับคนที่ดีบักโค้ดของคุณเพื่อเริ่มเห็น ZeroDivisionErrors จำนวนมาก
y = 1/0เป็นโซลูชันที่ชาญฉลาดมากหากประเภทข้อยกเว้นไม่เกี่ยวข้อง
เกี่ยวกับ:
lambda x: exec('raise(Exception(x))')
SyntaxErrorPython 2.7.11
ฟังก์ชันที่สร้างด้วยรูปแบบแลมบ์ดาไม่สามารถมีคำสั่งได้
จริงๆแล้วมีวิธี แต่มันถูกสร้างขึ้นมาก
คุณสามารถสร้างวัตถุรหัสโดยใช้compile()ฟังก์ชันในตัว สิ่งนี้ช่วยให้คุณสามารถใช้raiseคำสั่ง (หรือคำสั่งอื่น ๆ สำหรับเรื่องนั้น) แต่มันก่อให้เกิดความท้าทายอีกประการหนึ่งนั่นคือการเรียกใช้วัตถุรหัส วิธีปกติคือการใช้execคำสั่ง แต่จะทำให้คุณกลับไปสู่ปัญหาเดิมนั่นคือคุณไม่สามารถดำเนินการคำสั่งใน a lambda(หรือeval()สำหรับเรื่องนั้น) ได้
วิธีแก้คือแฮ็ค Callables เช่นผลลัพธ์ของlambdaคำสั่งล้วนมีแอตทริบิวต์__code__ซึ่งสามารถแทนที่ได้จริง ดังนั้นหากคุณสร้าง callable และแทนที่__code__ค่าด้วยวัตถุรหัสจากด้านบนคุณจะได้รับสิ่งที่สามารถประเมินได้โดยไม่ต้องใช้คำสั่ง อย่างไรก็ตามการบรรลุทั้งหมดนี้ส่งผลให้รหัสคลุมเครือมาก:
map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
ข้างต้นทำสิ่งต่อไปนี้:
การcompile()เรียกสร้างวัตถุรหัสที่ทำให้เกิดข้อยกเว้น
lambda: 0ผลตอบแทน callable ที่ไม่ทำอะไรเลย แต่กลับค่า 0 - นี้ถูกนำมาใช้ในการดำเนินการวัตถุรหัสดังกล่าวในภายหลัง
lambda x, y, zสร้างฟังก์ชั่นที่เรียกเป็น__setattr__วิธีการของอาร์กิวเมนต์แรกกับข้อโต้แย้งที่เหลือและผลตอบแทนอาร์กิวเมนต์แรก! นี้เป็นสิ่งจำเป็นเพราะ__setattr__ตัวเองส่งกลับNone;
การmap()เรียกใช้ผลลัพธ์ของlambda: 0และใช้lambda x, y, zแทนที่__code__อ็อบเจ็กต์ด้วยผลลัพธ์ของการcompile()โทร ผลลัพธ์ของการดำเนินการแผนที่นี้คือรายการที่มีรายการเดียวรายการที่ส่งคืนlambda x, y, zซึ่งเป็นสาเหตุที่เราต้องการสิ่งนี้lambda: ถ้าเราจะใช้__setattr__ทันทีเราจะสูญเสียการอ้างอิงถึงlambda: 0วัตถุ!
ในที่สุดองค์ประกอบแรก (และเท่านั้น) ของรายการที่ส่งคืนโดยการmap()เรียกจะถูกดำเนินการส่งผลให้มีการเรียกวัตถุรหัสในที่สุดก็เพิ่มข้อยกเว้นที่ต้องการ
ใช้งานได้ (ทดสอบใน Python 2.6) แต่ก็ไม่สวยแน่นอน
หมายเหตุสุดท้าย: หากคุณสามารถเข้าถึงtypesโมดูล (ซึ่งจะต้องใช้importคำสั่งก่อนหน้าของคุณeval) คุณสามารถย่อรหัสนี้ให้สั้นลงเล็กน้อยโดยใช้types.FunctionType()คุณสามารถสร้างฟังก์ชั่นที่จะดำเนินการกับวัตถุรหัสที่กำหนดดังนั้นคุณจะชนะ ไม่จำเป็นต้องแฮ็คในการสร้างฟังก์ชันหลอกlambda: 0และแทนที่ค่าของ__code__แอตทริบิวต์
หากสิ่งที่คุณต้องการคือนิพจน์แลมบ์ดาที่ทำให้เกิดข้อยกเว้นโดยพลการคุณสามารถทำได้ด้วยนิพจน์ที่ผิดกฎหมาย ตัวอย่างเช่นlambda x: [][0]จะพยายามเข้าถึงองค์ประกอบแรกในรายการว่างซึ่งจะเพิ่ม IndexError
โปรดทราบ : นี่คือการแฮ็กไม่ใช่คุณสมบัติ อย่าใช้สิ่งนี้ในรหัสใด ๆ (ที่ไม่ใช่โค้ดกอล์ฟ) ที่มนุษย์คนอื่นอาจเห็นหรือใช้
TypeError: <lambda>() takes exactly 1 positional argument (2 given). คุณแน่ใจกับ IndexError หรือไม่?
lambda *x: [][0]. (เวอร์ชันดั้งเดิมใช้อาร์กิวเมนต์เดียวเท่านั้นไม่มีอาร์กิวเมนต์ใช้lambda : [][0]สำหรับสองใช้lambda x,y: [][0]ฯลฯ )
lambda x: {}["I want to show this message. Called with: %s" % x] ผลิต: KeyError: 'I want to show this message. Called with: foo'
ฉันต้องการให้คำอธิบายเกี่ยวกับUPDATE 3ของคำตอบที่มาร์เซโลแคนโตส:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
lambda: 0เป็นตัวอย่างของbuiltins.functionชั้นเรียน
type(lambda: 0)คือbuiltins.functionชั้นเรียน
(lambda: 0).__code__เป็นcodeวัตถุ วัตถุเป็นวัตถุซึ่งถือ bytecode รวบรวมในสิ่งอื่น ๆ มันถูกกำหนดที่นี่ใน CPython https://github.com/python/cpython/blob/master/Include/code.h วิธีการจะดำเนินการที่นี่https://github.com/python/cpython/blob/master/Objects/codeobject.c เราสามารถเรียกใช้ความช่วยเหลือเกี่ยวกับวัตถุรหัส:code
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__)เป็นคลาสรหัส
ดังนั้นเมื่อเราพูด
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
เรากำลังเรียกตัวสร้างของวัตถุรหัสด้วยอาร์กิวเมนต์ต่อไปนี้:
คุณสามารถอ่านเกี่ยวกับสิ่งที่ขัดแย้งหมายถึงในความหมายของhttps://github.com/python/cpython/blob/master/Include/code.hPyCodeObject
ค่าของ 67 ที่อาร์กิวเมนต์เป็นตัวอย่างflagsCO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE
อาร์กิวเมนต์codestringอิมพอร์ตและส่วนใหญ่คืออ็อพชันคำสั่ง มาดูความหมายกัน
>>> import dis
>>> dis.dis(b'|\0\202\1\0')
0 LOAD_FAST 0 (0)
2 RAISE_VARARGS 1
4 <0>
เอกสารของ opcodes โดยสามารถพบได้ที่นี่
https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions ไบต์แรกคือ opcode สำหรับLOAD_FASTไบต์ที่สองคืออาร์กิวเมนต์คือ 0
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
ดังนั้นเราจึงดันการอ้างอิงไปxยังสแต็ก varnamesเป็นรายการของสตริงที่มีเพียง 'x' a เราจะพุชอาร์กิวเมนต์เดียวของฟังก์ชันที่เรากำหนดไปยังสแต็ก
ไบต์ถัดไปคือ opcode สำหรับRAISE_VARARGSและไบต์ถัดไปคืออาร์กิวเมนต์คือ 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 เป็นอันดับต้น ๆ ของกอง เนื่องจากเราพุชอาร์กิวเมนต์แรก ( x) ของฟังก์ชันของเราไปที่สแต็กและargcเป็น 1 เราจะเพิ่ม
xถ้าเป็นอินสแตนซ์ข้อยกเว้นหรือสร้างอินสแตนซ์xและเพิ่มเป็นอย่างอื่น
ไม่ได้ใช้ไบต์สุดท้ายคือ 0 ไม่ใช่รหัสที่ถูกต้อง มันอาจจะไม่มีด้วยเช่นกัน
กลับไปที่ข้อมูลโค้ดเรากำลังดำเนินการต่อไป:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
เราเรียกว่าตัวสร้างของวัตถุรหัส:
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
เราส่งผ่านวัตถุรหัสและพจนานุกรมว่างเปล่าไปยังตัวสร้างของวัตถุฟังก์ชัน:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)
ลองเรียกความช่วยเหลือเกี่ยวกับวัตถุฟังก์ชันเพื่อดูว่าอาร์กิวเมนต์หมายถึงอะไร
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
จากนั้นเราจะเรียกฟังก์ชันที่สร้างขึ้นผ่านอินสแตนซ์ Exception เป็นอาร์กิวเมนต์ ดังนั้นเราจึงเรียกว่าฟังก์ชันแลมด้าซึ่งทำให้เกิดข้อยกเว้น ลองเรียกใช้ตัวอย่างข้อมูลและดูว่าใช้งานได้จริงตามที่ตั้งใจไว้
>>> 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
เราเห็นว่าไบต์สุดท้ายของ bytecode นั้นไร้ประโยชน์ อย่ามายุ่งกับสำนวนที่ซับซ้อนนี้ ลองลบไบต์นั้นออก นอกจากนี้ถ้าเราต้องการเล่นกอล์ฟเพียงเล็กน้อยเราสามารถละเว้นการสร้างอินสแตนซ์ของ Exception และส่งผ่านคลาส Exception เป็นอาร์กิวเมนต์แทนได้ การเปลี่ยนแปลงเหล่านั้นจะส่งผลให้เกิดรหัสต่อไปนี้:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)
เมื่อเราเรียกใช้เราจะได้ผลลัพธ์เช่นเดิม มันสั้นกว่า