定义引发 Exception 的 lambda 表达式

我怎么写一个 lambda 表达式等价于:

def x():
raise Exception()

不得有下列情况:

y = lambda : raise Exception()
66249 次浏览

剥蟒蛇皮的方法不止一种:

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())

还有蟒蛇 强壮的胃溶液:

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 表单 不能包含语句创建的函数。

这个怎么样:

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

事实上,有一个办法,但是非常人为。

可以使用 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, zcompile()调用的结果替换它的 __code__对象。这个 map 操作的结果是一个包含一个条目的列表,这个条目由 lambda x, y, z返回,这就是为什么我们需要这个 lambda: 如果我们马上使用 __setattr__,我们将丢失对 lambda: 0对象的引用!

  • 最后,执行由 map()调用返回的列表的第一个(也是唯一的)元素,导致调用代码对象,最终引发所需的异常。

它可以工作(在 Python 2.6中测试) ,但是肯定不好看。

最后需要注意的是: 如果你可以访问 types模块(需要在 eval之前使用 import语句) ,那么你可以把这段代码缩短一点: 使用 types.FunctionType()你可以创建一个函数来执行给定的代码对象,所以你不需要用 lambda: 0创建一个虚函数并替换它的 __code__属性的值。

如果所需的只是引发任意异常的 lambda 表达式,则可以使用非法表达式完成此操作。例如,lambda x: [][0]将尝试访问空列表中的第一个元素,这将引发 IndexError。

请注意 : 这是一个黑客,而不是一个功能。不要在其他人可能看到或使用的任何(非代码-高尔夫)代码中使用这种方法。

我想对马塞洛 · 坎托斯提供的 更新3答案做一个解释:

type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

解释

lambda: 0builtins.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'')

我们使用以下参数调用代码对象的构造函数:

  • Argcount = 1
  • Kwonlyargcount = 0
  • Nlocal = 1
  • Stacksize = 1
  • 旗帜 = 67
  • Codestring = b’| 020210’
  • 常数 = ()
  • Name = ()
  • Varnames= (‘ x’,)
  • 文件名 =”
  • Name =”
  • Firstlineno = 1
  • Lnotab = 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_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实例,并以其他方式引发它。

不使用最后一个字节,即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 类作为参数传递 在以下代码:

type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)

当我们运行它的时候,我们会得到和以前一样的结果,只是短了一些。

每次我想这样做的时候,都是在一个测试中,我想断言一个函数没有被调用。

对于这个用例,我发现使用带有副作用的模拟会更清楚

from unittest.mock import Mock
MyClass.my_method = Mock(side_effect=AssertionError('we should not reach this method call')

它也可以在其他设置中工作,但是我不希望在我的主应用程序中依赖 unittest

以上所有的解决方案都是有效的,但我认为这个方案是最短的,以防你只是需要任何引发随机异常的函数:

lambda: 0/0

瞧!