>>>deffizzbuzz(n):... if n %3==0and n %5==0:... return'FizzBuzz'... elif n %3==0:... return'Fizz'... elif n %5==0:... return'Buzz'... else:... returnstr(n)...>>>for attr indir(fizzbuzz.__code__):... if attr.startswith('co_'):... print(f"{attr}:\t{getattr(fizzbuzz.__code__, attr)}")...co_argcount:1co_cellvars: ()co_code:b'|\x00d\x01\x16\x00d\x02k\x02r\x1c|\x00d\x03\x16\x00d\x02k\x02r\x1cd\x04S\x00|\x00d\x01\x16\x00d\x02k\x02r,d\x05S\x00|\x00d\x03\x16\x00d\x02k\x02r<d\x06S\x00t\x00|\x00\x83\x01S\x00d\x00S\x00'co_consts: (None,3,0,5,'FizzBuzz','Fizz','Buzz')co_filename:<stdin>co_firstlineno:1co_flags:67co_freevars: ()co_kwonlyargcount:0co_lnotab:b'\x00\x01\x18\x01\x04\x01\x0c\x01\x04\x01\x0c\x01\x04\x02'co_name: fizzbuzzco_names: ('str',)co_nlocals:1co_stacksize:2co_varnames: ('n',)
deff():print(c) a =1 b =3defg():print(a+b) c=2defh():print(a+b+c)
example.py 被编译后再进行反汇编,我们能够得到以下字节码:
代码清单5.10: example.py 模块代码块对应的字节码
10 LOAD_CONST 0 (<code object f at 0x000002D7ADE8E540, file "example.py", line 1>)2 LOAD_CONST 1 ('f')4 MAKE_FUNCTION 06 STORE_NAME 0 (f)8 LOAD_CONST 2 (None)10 RETURN_VALUE
字节偏移量为0的指令将加载一个代码对象,该对象存储为名称 f 使用 MAKE_FUNCTION 指令的函数定义。 清单5.11显示了此代码对象的内容。
代码清单5.11: example.py 模块代码对象的属性
co_argcount:0co_cellvars: ()co_code:b'd\x00d\x01\x84\x00Z\x00d\x02S\x00'co_consts: (<code object f at 0x000002D7ADE8E540, file "example.py", line 1>,'f',None)co_filename: example.pyco_firstlineno:1co_flags:64co_freevars: ()co_kwonlyargcount:0co_lnotab:b''co_name:<module>co_names: ('f',)co_nlocals:0co_stacksize:2co_varnames: ()
就像预期的那样,模块代码对象与参数相关的字段(co_argcount,co_kwonlyargcount)全为0。 co_code 字段包含字节码指令,如清单5.10所示。 co_consts 字段是一个有趣的字段。 字段中的常量是代码对象,名称为 f 和 None。 其中的代码对象是 example.py 中函数 f 对应的代码对象,值 “f” 是函数的名称,“None”是函数的返回值。回想一下,python 编译器会向没有一个没有返回值的代码对象添加 return None 语句。
请注意,在模块编译期间实际上并未创建函数对象。 我们所拥有的只是代码对象,函数实际上是在代码对象执行期间创建的,如清单5.10所示。如果检查一下函数 f 的代码对象的属性,会发现,实际上它也由其他代码对象(内部嵌套函数 g 对应的代码对象)组成,如代码清单5.12所示。
代码清单5.11: 函数 f 对应的代码对象的属性
co_argcount:0co_cellvars: ('a','b')co_code:b't\x00t\x01\x83\x01\x01\x00d\x01\x89\x00d\x02\x89\x01\x87\x00\x87\x01f\x02d\x03d\x04\x84\x08}\x00d\x00S\x00'co_consts: (None,1,3,<code object g at 0x000002D7ADE8E6F0, file "example.py", line 5>,'f.<locals>.g')co_filename: example.pyco_firstlineno:1co_flags:3co_freevars: ()co_kwonlyargcount:0co_lnotab:b'\x00\x01\x08\x01\x04\x01\x04\x01'co_name: fco_names: ('print','c')co_nlocals:1co_stacksize:3co_varnames: ('g',)
typedef struct {
PyObject_HEAD
int co_argcount; /* #arguments, except *args */
int co_kwonlyargcount; /* #keyword only arguments */
int co_nlocals; /* #local variables */
int co_stacksize; /* #entries needed for evaluation stack */
int co_flags; /* CO_..., see below */
int co_firstlineno; /* first source line number */
PyObject *co_code; /* instruction opcodes */
PyObject *co_consts; /* list (constants used) */
PyObject *co_names; /* list of strings (names used) */
PyObject *co_varnames; /* tuple of strings (local variable names) */
PyObject *co_freevars; /* tuple of strings (free variable names) */
PyObject *co_cellvars; /* tuple of strings (cell variable names) */
/* The rest aren't used in either hash or comparisons, except for co_name,
used in both. This is done to preserve the name and line number
for tracebacks and debuggers; otherwise, constant de-duplication
would collapse identical functions/lambdas defined on different lines.
*/
Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */
PyObject *co_filename; /* unicode (where it was loaded from) */
PyObject *co_name; /* unicode (name, for reference) */
PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See
Objects/lnotab_notes.txt for details. */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
/* Scratch space for extra data relating to the code object.
Type is a void* to keep the format private in codeobject.c to force
people to go through the proper APIs. */
void *co_extra;
} PyCodeObject;