定义引发异常的lambda表达式


137

我如何写一个等于的lambda表达式:

def x():
    raise Exception()

不允许以下内容:

y = lambda : raise Exception()

2
所以你不能那样做。使用正常功能。
DrTyrsa 2011年

1
给匿名函数命名有什么意义?
John La Rooy

2
@gnibbler您可以使用名称来引用该函数。y()比REPL中的(lambda:0)()更易于使用。
Thomas Jung,

那么y=lambda...over 的优势是def y:什么?
John La Rooy

@gnibbler一些上下文:我想定义一个函数def g(f,e),该函数在满意的情况下调用f,如果检测到错误,则调用e。根据情况,e可能引发异常或返回一些有效值。要使用g,我想写g(lambda x:x * 2,lambda e:提高e)或g(lambda x:x * 2,lambda e:0)。
Thomas Jung

Answers:


169

设置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


117
OMG是什么黑暗艺术?
CodeColorist 2014年

11
如果您不在乎抛出什么类型的异常,以下方法也可以工作:lambda: 1 / 0。最后,您将抛出一个ZeroDivisionError而不是一个常规异常。请记住,如果允许传播该异常,则对于调试您的代码以开始看到一堆ZeroDivisionError的人来说,这看起来很奇怪。
沃伦·斯潘塞

很棒的解决方案@WarrenSpencer。大多数代码没有很多零除错误,因此就像您可以自己选择类型一样独特。
jwg

2
y = 1/0如果异常类型无关,则是超级智能解决方案
Saher Ahwal '18

3
谁能说说“深色/强健胃”解决方案的实际情况?
decvalts

56

怎么样:

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

12
它很hacky,但是对于要在要模拟功能的地方编写测试,这很简单!
Kannan Ekanath

8
可行,但您不应该这样做。
augurar 2015年

1
这对我不起作用,我SyntaxError使用Python 2.7.11。
Nick Sweeting

我也在Python 2.7.5上收到上述错误(SyntaxError)
Dinesh

1
这是特定于python 3的,但是我不认为python 2允许这样做。
Saher Ahwal '18

16

实际上,有一种方法,但是它非常人为。

您可以使用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__属性值的技巧。



15

如果您想要的只是引发任意异常的lambda表达式,则可以使用非法表达式来实现。例如,lambda x: [][0]将尝试访问空列表中的第一个元素,这将引发IndexError。

请注意:这是黑客行为,而非功能。请勿在他人可能看到或使用的任何(非代码高尔夫球)代码中使用此代码。


在我的情况下,我得到:TypeError: <lambda>() takes exactly 1 positional argument (2 given)。您确定IndexError吗?
2013年

4
是的 您是否提供了错误数量的参数?如果您需要一个可以接受任意数量参数的lambda函数,请使用lambda *x: [][0]。(原始版本仅包含一个参数;对于无参数,请使用lambda : [][0];对于两个参数,请使用lambda x,y: [][0];等等。)
Kyle Strand 2013年

3
我对此进行了扩展: lambda x: {}["I want to show this message. Called with: %s" % x] 产生: KeyError: 'I want to show this message. Called with: foo'
ErlVolton 2014年

@ErVolton聪明!尽管在一次性脚本中以外的任何地方使用它似乎是一个可怕的想法……
Kyle Strand 2014年

我暂时在一个单元测试中使用了一个我不费心去真正模拟记录器的项目。如果您尝试记录错误或严重错误,则会引发此错误。所以...是可怕的,尽管双方同意:)
ErlVolton 2014年

10

我想解释一下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: 0builtins.function该类的一个实例。
type(lambda: 0)builtins.function班级。
(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)

当我们运行它时,我们将获得与以前相同的结果。它只是更短。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.