【问题标题】:Define a lambda expression that raises an Exception定义引发异常的 lambda 表达式
【发布时间】:2012-01-07 20:09:20
【问题描述】:

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

def x():
    raise Exception()

以下是不允许的:

y = lambda : raise Exception()

【问题讨论】:

  • 所以你不能那样做。使用普通函数。
  • 给匿名函数命名有什么意义?
  • @gnibbler 可以使用名称来引用函数。 y() 比 REPL 中的 (lambda : 0)() 更容易使用。
  • 那么y=lambda...def y: 有什么优势呢?
  • @gnibbler 一些上下文:我想定义一个函数 def g(f, e) 在快乐的情况下调用 f ,如果检测到错误则调用 e 。根据场景 e 可能引发异常或返回一些有效值。要使用 g,我想写 g(lambda x: x *2, lambda e: raise e) 或 g(lambda x: x * 2, lambda e: 0)。

标签: python


【解决方案1】:

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

lambda: 0/0

瞧!

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

      我想解释一下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/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
      • 标志=67
      • codestring=b'|\0\202\1\0'
      • 常量=()
      • names=()
      • varnames=('x',)
      • 文件名=''
      • 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_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。它不是有效的操作码。它也可能不存在。

      回到我们正在分析的代码 sn-p:

      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 函数。 让我们运行 sn-p,看看它确实按预期工作。

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

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 就我而言,我得到:TypeError: &lt;lambda&gt;() takes exactly 1 positional argument (2 given)。您确定 IndexError 吗?
        • 是的。您是否提供了错误数量的参数?如果您需要一个可以接受任意数量参数的 lambda 函数,请使用 lambda *x: [][0]。 (原版只带一个参数,不带参数的用lambda : [][0];两个用lambda x,y: [][0];以此类推)
        • 我已经扩展了一点:lambda x: {}["I want to show this message. Called with: %s" % x] 产生:KeyError: 'I want to show this message. Called with: foo'
        • @ErlVolton 聪明!尽管在除了一次性脚本之外的任何地方使用它似乎都是一个糟糕的主意......
        • 我暂时在一个项目的单元测试中使用,我没有费心对我的记录器进行真正的模拟。如果您尝试记录错误或严重,它会引发。所以......是的,很糟糕,虽然是自愿的:)
        【解决方案5】:

        给 Python 换肤的方法不止一种:

        y = lambda: (_ for _ in ()).throw(Exception('foobar'))
        

        Lambda 接受语句。由于raise ex 是一个语句,你可以写一个通用的raiser:

        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

        【讨论】:

        • 天哪,这是什么黑暗艺术?
        • 如果你不关心抛出什么类型的异常,下面的方法也可以:lambda: 1 / 0。您最终会抛出 ZeroDivisionError 而不是常规异常。请记住,如果允许传播异常,那么调试您的代码以开始看到一堆 ZeroDivisionErrors 的人可能看起来很奇怪。
        • y = 1/0 在异常类型无关的情况下是超级聪明的解决方案
        • 谁能告诉我们“黑暗艺术/强胃”解决方案的实际情况?
        • “强胃”解决方案是一些很棒的代码高尔夫!有关详细信息,请在 Google 和笔记本中搜索字节码 help(type(lambda: 0)) help((lambda: 0).__code__) 并查看模块 dis。有两个很酷的部分。内置类 functioncode 没有公开,否则它将是 function(code( 13 arguments),{})(Exception)lambda: 0 只返回一个function 实例,我们需要该类,因此需要type(lambda: 0)。另一种方法是(lambda:0).__class__help(code)dis 解释了 13 个 args,但 b'|\0\202\1\0' 是字节码字符串,见 compile
        【解决方案6】:

        其实是有办法的,但是很做作。

        您可以使用compile() 内置函数创建代码对象。这允许您使用raise 语句(或任何其他语句,就此而言),但它提出了另一个挑战:执行代码对象。通常的方法是使用exec 语句,但这会让你回到原来的问题,即你不能在lambda(或eval(),就此而言)执行语句。

        解决方案是 hack。像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__,我们将失去对@987654344 的引用@对象!

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

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

        最后一点:如果您可以访问types 模块(这需要在eval 之前使用import 语句),那么您可以稍微缩短此代码:使用types.FunctionType() you可以创建一个将执行给定代码对象的函数,因此您不需要使用 lambda: 0 创建一个虚拟函数并替换其 __code__ 属性的值。

        【讨论】:

          【解决方案7】:

          怎么样:

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

          【讨论】:

          • 这很hacky,但是对于编写要模拟函数的测试来说,这很有效!!!
          • 有效,但您不应该这样做。
          • 这对我不起作用,我在 Python 2.7.11 上得到了 SyntaxError
          • 这是特定于 python 3 但我认为 python 2 不允许这样做。
          • @augurar 为什么我们不应该使用这个解决方案?如果是因为exec() 是危险的,那么当exec() 的参数被硬编码时,这是否相关?如果我对代码库有足够的访问权限来更改该字符串,那么在上面添加一行来读取import os; os.system('destroy all the things') 将同样容易。还有其他我不知道您为什么建议反对的原因吗?教科书总是停留在“Just don't”,这无助于任何人了解可能存在的危险。
          【解决方案8】:

          使用 lambda 形式创建的函数cannot contain statements

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-19
          • 2019-11-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多