问题:定义引发异常的lambda表达式
我如何写一个等于的lambda表达式:
def x():
raise Exception()
不允许以下内容:
y = lambda : raise Exception()
回答 0
设置Python皮肤的方法有多种:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Lambda接受语句。既然raise ex
是一条语句,您可以编写一个通用的提升器:
def raise_(ex):
raise ex
y = lambda: raise_(Exception('foobar'))
但是,如果您的目标是避免使用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
。
回答 1
怎么样:
lambda x: exec('raise(Exception(x))')
回答 2
实际上,有一种方法,但是它非常人为。
您可以使用compile()
内置函数创建代码对象。这使您可以使用raise
语句(或其他任何语句),但这又带来了另一个挑战:执行代码对象。通常的方法是使用该exec
语句,但这会使您回到最初的问题,即您不能在lambda
(或)中执行语句eval()
。
解决的办法是破解。诸如lambda
语句结果之类的可调用对象均具有属性__code__
,该属性实际上可以被替换。因此,如果您创建一个可调用__code__
对象并将其值替换为上面的代码对象,则可以得到无需使用语句即可进行评估的内容。但是,实现所有这些都会导致代码非常晦涩:
map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
上面执行以下操作:
该
compile()
调用创建一个引发异常的代码对象;所述
lambda: 0
返回一个可调用什么也不做而返回值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__
属性值的技巧。
回答 3
用lambda表单创建的函数不能包含语句。
回答 4
如果您想要的只是引发任意异常的lambda表达式,则可以使用非法表达式来实现。例如,lambda x: [][0]
将尝试访问空列表中的第一个元素,这将引发IndexError。
请注意:这是黑客行为,而非功能。请勿在他人可能看到或使用的任何(非代码高尔夫球)代码中使用此代码。
回答 5
我想解释一下Marcelo Cantos提供的答案的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
Class。(lambda: 0).__code__
是一个code
对象。
甲code
对象是保存除了其他方面,编译的字节代码的对象。它在CPython https://github.com/python/cpython/blob/master/Include/include.code中定义。其方法在此处https://github.com/python/cpython/blob/master/Objects/codeobject.c中实现。我们可以在代码对象上运行帮助:
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
- codestring = b’| \ 0 \ 202 \ 1 \ 0′
- 常数=()
- 名称=()
- varnames =(’x’,)
- 文件名=”
- 名称=”
- firstlineno = 1
- lnotab = b”
您可以在PyCodeObject
https://github.com/python/cpython/blob/master/Include/include.code的定义中了解自变量的含义。flags
例如,参数的值67CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE
。
最多importand参数是,codestring
其中包含指令操作码。让我们看看它们的含义。
>>> import dis
>>> dis.dis(b'|\0\202\1\0')
0 LOAD_FAST 0 (0)
2 RAISE_VARARGS 1
4 <0>
可以在以下网址找到操作码的文档:
https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions。第一个字节是的操作码LOAD_FAST
,第二个字节是其参数,即0。
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
因此,我们将引用x
推入堆栈。的varnames
是只含有“X”的字符串列表。我们将把要定义的函数的唯一参数推入堆栈。
下一个字节是其操作码,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来了解参数的含义。
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实例作为参数的构造函数。因此,我们调用了引发异常的lambda函数。让我们运行代码段,看看它确实按预期工作。
>>> 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
改进措施
我们看到字节码的最后节是无用的。让我们不要轻易将这个复杂的表达式弄乱。让我们删除该字节。另外,如果我们想打高尔夫球,我们可以省略Exception的实例化,而是将Exception类作为参数传递。这些更改将导致以下代码:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)
当我们运行它时,我们将获得与以前相同的结果。它只是更短。