定义一个引发Exception的lambda表达式

时间:2011-11-28 10:41:33

标签: python

如何编写一个等同于:

的lambda表达式
def x():
    raise Exception()

不允许以下内容:

y = lambda : raise Exception()

8 个答案:

答案 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: 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
  • nlocals = 1
  • stacksize = 1
  • flags = 67
  • codestring = b'| \ 0 \ 202 \ 1 \ 0'
  • constants =()
  • names =()
  • varnames =('x',)
  • filename =''
  • name =''
  • firstlineno = 1
  • lnotab = b''

您可以了解PyCodeObject的定义中参数的含义 https://github.com/python/cpython/blob/master/Include/code.hflags参数的值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

等等!