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

Tho*_*ung 120 python

我怎么能写一个相当于的lambda表达式:

def x():
    raise Exception()
Run Code Online (Sandbox Code Playgroud)

以下是不允许的:

y = lambda : raise Exception()
Run Code Online (Sandbox Code Playgroud)

Mar*_*tos 140

更新2:我错了!事实证明,为Python设置皮肤的方法不止一种:

y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Run Code Online (Sandbox Code Playgroud)

不,Lambdas只接受表达式. raise ex是一份声明.当然,你可以写一个通用的提升者:

def raise_(ex):
    raise ex

y = lambda: raise_(Exception('foobar'))
Run Code Online (Sandbox Code Playgroud)

但如果你的目标是避免a def,这显然不会削减它.但它允许您有条件地引发异常,例如:

y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))
Run Code Online (Sandbox Code Playgroud)

更新:好的,所以你可以在不定义命名函数的情况下引发异常.你需要的只是一个强大的胃(和给定代码的2.x):

type(lambda:0)(type((lambda:0).func_code)(
  1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())
Run Code Online (Sandbox Code Playgroud)

更新3:和python3 强胃解决方案:

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

更新4:感谢@WarrenSpencer指出一个非常简单的答案,如果你不关心引发的异常:y = lambda: 1/0.

  • OMG是什么黑暗艺术? (89认同)
  • +1更新.确实肚子很强! (16认同)
  • 谁能告诉我们“黑暗艺术/强胃”解决方案的实际情况? (10认同)
  • 如果您不关心抛出什么类型的异常,以下也可以:`lambda:1/0`.您最终会抛出ZeroDivisionError而不是常规异常.请记住,如果允许异常传播,调试代码的人可能看起来很奇怪,开始看到一堆ZeroDivisionErrors. (6认同)
  • “强健胃部”的解决方案是一些很棒的代码高尔夫!有关详细信息,请在 Google 和笔记本中搜索字节码 `help(type(lambda: 0)) ` 和 `help((lambda: 0).__code__)` 并查看模块 `dis`。有两个很酷的部分。内置类 `function` 和 `code` 没有公开,否则它将是 `function(code( 13 argument),{})(Exception)`。`lambda: 0` 只是返回一个 `function` 实例,我们需要该类,因此是 `type(lambda: 0)`。另一种选择是“(lambda:0).__class__”。`help(code)` 和 `dis` 解释了 13 个参数,但 `b'|\0\202\1\0'` 是字节码字符串,参见 `compile` (4认同)
  • 如果异常类型无关紧要,`y = 1/0` 是超级智能的解决方案 (3认同)
  • 我想你也可以对“IndexError”执行“lambda: [][1]”,对“KeyError”执行“lambda: {}['']”,对“TypeError”执行“lambda: ''+0”等。 (3认同)

vvk*_*wss 50

怎么样:

lambda x: exec('raise(Exception(x))')
Run Code Online (Sandbox Code Playgroud)

  • 这是非常hacky但是为了编写你想要模拟函数的测试,这个工作很整洁! (9认同)
  • 工作,但你不应该这样做. (7认同)
  • @augurar 为什么我们不应该使用这个解决方案?如果是因为“exec()”是危险的,那么当“exec()”的参数被硬编码时,这是否相关?如果我有足够的权限访问代码库来更改该字符串,则在上面添加一行以读取“import os;” os.system('销毁所有东西')` 也同样简单。我不知道你为什么建议反对这个还有其他原因吗?教科书总是停留在“不要这样做”,这无助于任何人了解可能存在的危险。 (5认同)

kat*_*tsu 17

我想解释一下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())
Run Code Online (Sandbox Code Playgroud)

解释

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.
Run Code Online (Sandbox Code Playgroud)

type((lambda: 0).__code__)是代码类。
所以当我们说

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.
Run Code Online (Sandbox Code Playgroud)

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

  • 参数数=1
  • kwonlyargcount=0
  • nlocals=1
  • 堆栈大小=1
  • 标志=67
  • 代码串=b'|\0\202\1\0'
  • 常数=()
  • 姓名=()
  • varnames=('x',)
  • 文件名=''
  • 名称=''
  • 第一行no=1
  • lntab=b''

您可以在PyCodeObject https://github.com/python/cpython/blob/master/Include/code.h的定义中了解参数的含义。flags例如,参数的值 67是CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE

最重要的参数是codestring包含指令操作码的 。让我们看看它们是什么意思。

type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
Run Code Online (Sandbox Code Playgroud)

操作码的文档可以在这里找到 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.
Run Code Online (Sandbox Code Playgroud)

所以我们将引用压入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)
Run Code Online (Sandbox Code Playgroud)

TOS 是栈顶的。由于我们将x函数的第一个参数 ( )压入堆栈并且argc为 1,因此x如果它是异常实例,我们将引发 , 或者创建一个实例,x否则将引发它。

不使用最后一个字节,即 0。它不是有效的操作码。它也可能不在那里。

回到我们正在分析的代码片段:

>>> import dis
>>> dis.dis(b'|\0\202\1\0')
          0 LOAD_FAST                0 (0)
          2 RAISE_VARARGS            1
          4 <0>
Run Code Online (Sandbox Code Playgroud)

我们调用了代码对象的构造函数:

LOAD_FAST(var_num)
    Pushes a reference to the local co_varnames[var_num] onto the stack.
Run Code Online (Sandbox Code Playgroud)

我们将代码对象和一个空字典传递给函数对象的构造函数:

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)
Run Code Online (Sandbox Code Playgroud)

让我们在函数对象上调用 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
Run Code Online (Sandbox Code Playgroud)

然后我们调用构造函数,传递一个 Exception 实例作为参数。因此,我们调用了一个引发异常的 lambda 函数。让我们运行代码片段,看看它确实按预期工作。

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

改进

我们看到字节码的最后一个字节是无用的。让我们不要把这个复杂的表达方式杂乱无章。让我们删除那个字节。此外,如果我们想打高尔夫球,我们可以省略 Exception 的实例化,而是将 Exception 类作为参数传递。这些更改将导致以下代码:

type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
Run Code Online (Sandbox Code Playgroud)

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


小智 16

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

您可以使用compile()内置函数创建代码对象.这允许您使用raise语句(或任何其他语句),但它提出了另一个挑战:执行代码对象.通常的方法是使用该exec语句,但这会导致您回到原始问题,即您无法在lambda(或者)中执行语句eval().

解决方案是一个黑客.像一个lambda语句的结果的Callables 都有一个属性__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()调用.这个map操作的结果是一个包含一个条目的列表,返回的lambda x, y, z是一个条目,这就是为什么我们需要这个lambda:如果我们__setattr__马上使用,我们就会丢失lambda: 0对象的引用!

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

它工作(在Python 2.6中测试),但它绝对不漂亮.

最后一点说明:如果你有权访问该types模块(需要import在你之前使用该语句eval),那么你可以稍微缩短这段代码:使用types.FunctionType()你可以创建一个执行给定代码对象的函数,这样你就赢了不需要创建虚函数lambda: 0并替换其__code__属性的值.


Kyl*_*and 15

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

请注意:这是一个黑客,而不是一个功能.不要使用这是另一个人可能看到或使用的任何(非代码 - 高尔夫)代码.

  • 是的.您是否提供了错误数量的参数?如果你需要一个可以接受任意数量参数的lambda函数,请使用`lambda*x:[] [0]`.(原始版本只接受一个参数;对于没有参数,使用`lambda:[] [0]`;对于两个,使用`lambda x,y:[] [0]`;等等) (4认同)
  • 我已经扩展了一点:`lambda x:{} ["我想显示这条消息.调用:%s"%x]`生成:`KeyError:'我想显示此消息.叫:foo'` (3认同)

Mit*_*tar 13

使用lambda表单创建的函数不能包含语句.

  • 的确是这样,但是并不能真正回答问题。从lambda表达式引发异常很容易(有时可以通过使用非常人为的技巧来捕获它,请参见/sf/answers/3564168051/或https://stackoverflow.com/a/ 50961836/2560053)。 (2认同)

Atn*_*Atn 11

每次我想这样做时,都是在测试中我想断言未调用函数。

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

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

它也适用于其他设置,但我不想在我的主应用程序中依赖单元测试


Nad*_*oul 9

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

lambda: 0/0
Run Code Online (Sandbox Code Playgroud)

瞧!