กำหนดนิพจน์แลมบ์ดาที่ทำให้เกิดข้อยกเว้น


144

ฉันจะเขียนนิพจน์แลมบ์ดาที่เทียบเท่ากับ:

def x():
    raise Exception()

ไม่อนุญาตสิ่งต่อไปนี้:

y = lambda : raise Exception()

3
คุณจึงไม่สามารถทำเช่นนั้นได้ ใช้ฟังก์ชันปกติ
DrTyrsa

1
จุดประสงค์ของการตั้งชื่อให้กับฟังก์ชันนิรนามคืออะไร?
John La Rooy

2
@gnibbler คุณสามารถใช้ชื่อเพื่ออ้างถึงฟังก์ชัน y () ใช้งานง่ายกว่า (lambda: 0) () ใน REPL
Thomas Jung

ดังนั้นสิ่งที่เป็นข้อได้เปรียบของy=lambda...มากกว่าdef y:นั้น?
John La Rooy

@gnibbler บริบทบางอย่าง: ฉันต้องการกำหนดฟังก์ชัน def g (f, e) ที่เรียกใช้ f ในกรณีที่มีความสุขและ e หากตรวจพบข้อผิดพลาด ขึ้นอยู่กับสถานการณ์ที่ e สามารถเพิ่มข้อยกเว้นหรือส่งคืนค่าที่ถูกต้องได้ ในการใช้ g ฉันต้องการเขียน g (lambda x: x * 2, lambda e: Raise e) หรือ g (lambda x: x * 2, lambda e: 0)
Thomas Jung

คำตอบ:


173

มีหลายวิธีในการสกิน 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บ้าง:


127
OMG ศิลปะมืดมันคืออะไร?
CodeColorist

14
lambda: 1 / 0หากคุณไม่สนใจสิ่งที่ประเภทของการยกเว้นจะโยนต่อไปยังการทำงาน: คุณจะจบลงด้วย ZeroDivisionError ที่ถูกโยนแทนที่จะเป็นข้อยกเว้นปกติ โปรดทราบว่าหากข้อยกเว้นได้รับอนุญาตให้โพรโทเกตอาจดูแปลกสำหรับคนที่ดีบักโค้ดของคุณเพื่อเริ่มเห็น ZeroDivisionErrors จำนวนมาก
Warren Spencer

ทางออกที่ดี @WarrenSpencer โค้ดส่วนใหญ่ไม่มีข้อผิดพลาดในการหารศูนย์ดังนั้นจึงมีความโดดเด่นราวกับว่าคุณสามารถเลือกประเภทได้ด้วยตัวเอง
jwg

2
y = 1/0เป็นโซลูชันที่ชาญฉลาดมากหากประเภทข้อยกเว้นไม่เกี่ยวข้อง
Saher Ahwal

5
มีใครช่วยเล่าให้เราฟังถึงสิ่งที่เกิดขึ้นจริงในการแก้ปัญหา 'ศิลปะมืด / ท้องแข็ง'?
decvalts

60

เกี่ยวกับ:

lambda x: exec('raise(Exception(x))')

12
มันค่อนข้างแฮ็ค แต่สำหรับการทดสอบการเขียนที่คุณต้องการจำลองฟังก์ชันนี้ทำงานได้อย่างเรียบร้อย !!!
กรรณเอกนาถ

10
ใช้ได้ผล แต่คุณไม่ควรทำ
augurar

1
สิ่งนี้ไม่ได้ผลสำหรับฉันฉันได้รับSyntaxErrorPython 2.7.11
Nick Sweeting

ฉันยังได้รับข้อผิดพลาดข้างต้น (SyntaxError) บน Python 2.7.5
Dinesh

1
นี่เป็นสิ่งเฉพาะสำหรับ python 3 แต่ฉันไม่คิดว่า python 2 อนุญาต
Saher Ahwal

16

ฟังก์ชันที่สร้างด้วยรูปแบบแลมบ์ดาไม่สามารถมีคำสั่งได้


2
นี่เป็นเรื่องจริง แต่ไม่ได้ตอบคำถามจริงๆ การเพิ่มข้อยกเว้นจากนิพจน์แลมบ์ดานั้นทำได้ง่าย (และบางครั้งการจับมันอาจทำได้โดยใช้กลอุบายที่กำหนดขึ้นมากโปรดดูstackoverflow.com/a/50916686/2560053หรือstackoverflow.com/a/50961836/2560053 )
Thomas Baruchel

16

จริงๆแล้วมีวิธี แต่มันถูกสร้างขึ้นมาก

คุณสามารถสร้างวัตถุรหัสโดยใช้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__แอตทริบิวต์


15

หากสิ่งที่คุณต้องการคือนิพจน์แลมบ์ดาที่ทำให้เกิดข้อยกเว้นโดยพลการคุณสามารถทำได้ด้วยนิพจน์ที่ผิดกฎหมาย ตัวอย่างเช่นlambda x: [][0]จะพยายามเข้าถึงองค์ประกอบแรกในรายการว่างซึ่งจะเพิ่ม IndexError

โปรดทราบ : นี่คือการแฮ็กไม่ใช่คุณสมบัติ อย่าใช้สิ่งนี้ในรหัสใด ๆ (ที่ไม่ใช่โค้ดกอล์ฟ) ที่มนุษย์คนอื่นอาจเห็นหรือใช้


ในกรณีของฉันฉันได้รับ: TypeError: <lambda>() takes exactly 1 positional argument (2 given). คุณแน่ใจกับ IndexError หรือไม่?
Jovik

4
ใช่. คุณระบุจำนวนอาร์กิวเมนต์ผิดหรือเปล่า หากคุณต้องการฟังก์ชันแลมบ์ดาที่สามารถรับอาร์กิวเมนต์จำนวนเท่าใดก็ได้ให้ใช้lambda *x: [][0]. (เวอร์ชันดั้งเดิมใช้อาร์กิวเมนต์เดียวเท่านั้นไม่มีอาร์กิวเมนต์ใช้lambda : [][0]สำหรับสองใช้lambda x,y: [][0]ฯลฯ )
Kyle Strand

4
ฉันขยายสิ่งนี้เล็กน้อย: lambda x: {}["I want to show this message. Called with: %s" % x] ผลิต: KeyError: 'I want to show this message. Called with: foo'
ErlVolton

@ErlVolton ฉลาด! แม้ว่าจะใช้สิ่งนี้ได้ทุกที่ยกเว้นในสคริปต์ครั้งเดียวดูเหมือนเป็นความคิดที่แย่มาก ...
Kyle Strand

ฉันใช้ชั่วคราวในการทดสอบหน่วยสำหรับโครงการที่ฉันไม่ได้ใส่ใจที่จะเลียนแบบคนตัดไม้ของฉันจริงๆ จะเพิ่มขึ้นหากคุณพยายามบันทึกข้อผิดพลาดหรือวิกฤต ใช่แย่มากแม้ว่าจะยินยอม :)
ErlVolton

14

ฉันต้องการให้คำอธิบายเกี่ยวกับ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'')

เรากำลังเรียกตัวสร้างของวัตถุรหัสด้วยอาร์กิวเมนต์ต่อไปนี้:

  • argcount = 1
  • kwonlyargcount = 0
  • nlocals = 1
  • stacksize = 1
  • ธง = 67
  • การเข้ารหัส = b '| \ 0 \ 202 \ 1 \ 0'
  • ค่าคงที่ = ()
  • ชื่อ = ()
  • varnames = ('x',)
  • ชื่อไฟล์ = ''
  • ชื่อ = ''
  • firstlineno = 1
  • lnotab = 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)

เมื่อเราเรียกใช้เราจะได้ผลลัพธ์เช่นเดิม มันสั้นกว่า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.