我想解释一下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: 0 是 builtins.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.h。
flags 参数的值为 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)
当我们运行它时,我们会得到和以前一样的结果。它只是更短。