定义引发异常的lambda表达式

问题:定义引发异常的lambda表达式

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

def x():
    raise Exception()

不允许以下内容:

y = lambda : raise Exception()

How can I write a lambda expression that’s equivalent to:

def x():
    raise Exception()

The following is not allowed:

y = lambda : raise Exception()

回答 0

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

There is more than one way to skin a Python:

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

Lambdas accept statements. Since raise ex is a statement, you could write a general purpose raiser:

def raise_(ex):
    raise ex

y = lambda: raise_(Exception('foobar'))

But if your goal is to avoid a def, this obviously doesn’t cut it. It does, however allow you to conditionally raise exceptions, e.g.:

y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))

Alternatively you can raise an exception without defining a named function. All you need is a strong stomach (and 2.x for the given code):

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

And a python3 strong stomach solution:

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

Thanks @WarrenSpencer for pointing out a very simple answer if you don’t care which exception is raised: y = lambda: 1/0.


回答 1

怎么样:

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

How about:

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

回答 2

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

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

Actually, there is a way, but it’s very contrived.

You can create a code object using the compile() built-in function. This allows you to use the raise statement (or any other statement, for that matter), but it raises another challenge: executing the code object. The usual way would be to use the exec statement, but that leads you back to the original problem, namely that you can’t execute statements in a lambda (or an eval(), for that matter).

The solution is a hack. Callables like the result of a lambda statement all have an attribute __code__, which can actually be replaced. So, if you create a callable and replace it’s __code__ value with the code object from above, you get something that can be evaluated without using statements. Achieving all this, though, results in very obscure code:

map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()

The above does the following:

  • the compile() call creates a code object that raises the exception;

  • the lambda: 0 returns a callable that does nothing but return the value 0 — this is used to execute the above code object later;

  • the lambda x, y, z creates a function that calls the __setattr__ method of the first argument with the remaining arguments, AND RETURNS THE FIRST ARGUMENT! This is necessary, because __setattr__ itself returns None;

  • the map() call takes the result of lambda: 0, and using the lambda x, y, z replaces it’s __code__ object with the result of the compile() call. The result of this map operation is a list with one entry, the one returned by lambda x, y, z, which is why we need this lambda: if we would use __setattr__ right away, we would lose the reference to the lambda: 0 object!

  • finally, the first (and only) element of the list returned by the map() call is executed, resulting in the code object being called, ultimately raising the desired exception.

It works (tested in Python 2.6), but it’s definitely not pretty.

One last note: if you have access to the types module (which would require to use the import statement before your eval), then you can shorten this code down a bit: using types.FunctionType() you can create a function that will execute the given code object, so you won’t need the hack of creating a dummy function with lambda: 0 and replacing the value of its __code__ attribute.


回答 3

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

Functions created with lambda forms cannot contain statements.


回答 4

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

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

If all you want is a lambda expression that raises an arbitrary exception, you can accomplish this with an illegal expression. For instance, lambda x: [][0] will attempt to access the first element in an empty list, which will raise an IndexError.

PLEASE NOTE: This is a hack, not a feature. Do not use this in any (non code-golf) code that another human being might see or use.


回答 5

我想解释一下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.functionClass。
(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)

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

I’d like to give an explanation of the UPDATE 3 of the answer provided by Marcelo Cantos:

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

Explanation

lambda: 0 is an instance of the builtins.function class.
type(lambda: 0) is the builtins.function class.
(lambda: 0).__code__ is a code object.
A code object is an object which holds the compiled bytecode among other things. It is defined here in CPython https://github.com/python/cpython/blob/master/Include/code.h. Its methods are implemented here https://github.com/python/cpython/blob/master/Objects/codeobject.c. We can run the help on the code object:

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__) is the code class.
So when we say

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

we are calling the constructor of the code object with the following arguments:

  • 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”

You can read about what the arguments mean in the definition of the PyCodeObject https://github.com/python/cpython/blob/master/Include/code.h. The value of 67 for the flags argument is for example CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.

The most importand argument is the codestring which contains instruction opcodes. Let’s see what they mean.

>>> import dis
>>> dis.dis(b'|\0\202\1\0')
          0 LOAD_FAST                0 (0)
          2 RAISE_VARARGS            1
          4 <0>

The documentation of opcodes can by found here https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions. The first byte is the opcode for LOAD_FAST, the second byte is its argument i.e. 0.

LOAD_FAST(var_num)
    Pushes a reference to the local co_varnames[var_num] onto the stack.

So we push the reference to x onto the stack. The varnames is a list of strings containing only ‘x’. We will push the only argument of the function we are defining to the stack.

The next byte is the opcode for RAISE_VARARGS and the next byte is its argument i.e. 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)

The TOS is the top-of-stack. Since we pushed the first argument (x) of our function to the stack and argc is 1 we will raise the x if it is an exception instance or make an instance of x and raise it otherwise.

The last byte i.e. 0 is not used. It is not a valid opcode. It might as well not be there.

Going back to code snippet we are anylyzing:

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

We called the constructor of the code object:

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

We pass the code object and an empty dictionary to the constructor of a function object:

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

Let’s call help on a function object to see what the arguments mean.

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

We then call the constructed function passing an Exception instance as an argument. Consequently we called a lambda function which raises an exception. Let’s run the snippet and see that it indeed works as intended.

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

Improvements

We saw that the last byte of the bytecode is useless. Let’s not clutter this complicated expression needlesly. Let’s remove that byte. Also if we want to golf a little we could omit the instantiation of Exception and instead pass the Exception class as an argument. Those changes would result in the following code:

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

When we run it we will get the same result as before. It’s just shorter.