如何编写一个等同于:
的lambda表达式def x():
raise Exception()
不允许以下内容:
y = lambda : raise Exception()
答案 0 :(得分:130)
更新2:我错了!事实证明,对Python进行换肤的方法不止一种:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
号。 Lambdas只接受表达式。 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())
更新3:以及python3 强健的胃解决方案:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
更新4 :如果你不关心引发哪个异常,感谢@WarrenSpencer指出了一个非常简单的答案:y = lambda: 1/0
。
答案 1 :(得分:44)
怎么样:
lambda x: exec('raise(Exception(x))')
答案 2 :(得分:15)
实际上,有一种方法,但它非常人为。
您可以使用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 :(得分:13)
使用lambda创建的函数形成cannot contain statements。
答案 4 :(得分:13)
如果你想要的只是一个引发任意异常的lambda表达式,你可以使用非法表达式完成此操作。例如,lambda x: [][0]
将尝试访问空列表中的第一个元素,这将引发IndexError。
请注意:这是一个黑客,而不是一个功能。 不要使用这是其他人可能会看到或使用的任何(非代码 - 高尔夫)代码。
答案 5 :(得分:1)
我想解释一下Marcelo Cantos提供的答案的更新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
对象。
code
对象是保存编译后的字节码等的对象。
它在CPython https://github.com/python/cpython/blob/master/Include/code.h中定义。
其方法在此处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'')
我们使用以下参数调用代码对象的构造函数:
您可以了解PyCodeObject
的定义中参数的含义
https://github.com/python/cpython/blob/master/Include/code.h。
flags
参数的值67例如是CO_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>
操作码的文档可以在这里找到
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_VARAGS
的操作码,下一个字节是其参数(即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实例作为参数的构造函数。 因此,我们调用了引发异常的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)
当我们运行它时,我们将获得与以前相同的结果。它只是更短。
答案 6 :(得分:0)
每次我想这样做时,都是在测试中我想断言未调用函数。
对于这种用例,我发现使用带有副作用的模拟程序更加清楚
from unittest.mock import Mock
MyClass.my_method = Mock(side_effect=AssertionError('we should not reach this method call')
它也可以在其他设置中工作,但是我宁愿不依赖我的主应用程序中的单元测试
答案 7 :(得分:0)
上述所有解决方案都有效,但我认为这是最短的,以防您只需要任何引发随机异常的函数:
lambda: 0/0
等等!