Python 中的代码对象 code object
与 __code__
属性
0. 参考资料
- What is a code object in Python?
(本文大部分借鉴、翻译自这篇文章) - inspect — Inspect live objects
- PyCodeObject与Python程序执行
1. 概念
代码对象 code object
是一段可执行的 Python
代码在 CPython
中的内部表示。
可执行的 Python
代码包括:
- 函数
- 模块
- 类
- 生成器表达式
当你运行一段代码时,它被解析并编译成代码对象,随后被 CPython
虚拟机执行。
代码对象包含一系列直接操作虚拟机内部状态的指令。
这跟你在用 C
语言编程时是类似的,你写出人类可读的文本,然后用编译器转换成二进制形式,二进制代码(C
的机器码或者是 Python
的字节码)被 CPU
(对于 C
语言来说)或者 CPython
虚拟机虚拟的 CPU
直接执行。
代码对象除了包含 指令,还提供了虚拟机运行代码所需要的一些 额外信息。
2. 探索
以下的内容是在 Python 3.7
中实验的,而且主要是针对于函数来讲。至于模块和类虽然也是通过代码对象实现的(实际上,.pyc
文件里面就存放着序列化的模块代码对象),但是代码对象的大多数特性主要和函数相关。
关于版本需要注意两点:
- 在
Python 2
中,函数的代码对象通过函数.func_code
来访问;而Python 3
中,则需要通过函数.__code__
来访问。 Python 3
的代码对象增加了一个新属性co_kwonlyargcount
,对应强制关键字参数keyword-only argument
。
首先在控制台找出属于 函数.__code__
的所有不以双下划线开头的属性,一共有 15
个。
>>> li = [i for i in dir((lambda: 0).__code__) if not i.startswith('__')]
>>> print(li)
['co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> len(li)
15
以下内容来自官方文档:
属性 | 描述 |
---|---|
co_argcount |
number of arguments (not including keyword only arguments, * or ** args) |
co_code |
string of raw compiled bytecode |
co_cellvars |
tuple of names of cell variables (referenced by containing scopes) |
co_consts |
tuple of constants used in the bytecode |
co_filename |
name of file in which this code object was created |
co_firstlineno |
number of first line in Python source code |
co_flags |
bitmap of CO_* flags, read more here |
co_lnotab |
encoded mapping of line numbers to bytecode indices |
co_freevars |
tuple of names of free variables (referenced via a function’s closure) |
co_kwonlyargcount |
number of keyword only arguments (not including ** arg) |
co_name |
name with which this code object was defined |
co_names |
tuple of names of local variables |
co_nlocals |
number of local variables |
co_stacksize |
virtual machine stack space required |
co_varnames |
tuple of names of arguments and local variables |
下面逐个解释:
co_argcount
:函数接收参数的个数,不包括*args
和**kwargs
以及强制关键字参数。
>>> def test(a, b, c, d=1, e=2, *args, f=3, g, h=4, **kwargs):
... print(a, b, c, d, e, f, g, h, args, kwargs)
...
>>> code_obj = test.__code__
>>> code_obj.co_argcount
5
co_code
:二进制格式的字节码bytecode
,以字节串bytes
的形式存储(在Python 2
中以str
类型存储)。它为虚拟机提供一系列的指令。函数从第一条指令开始执行,在碰到RETURN_VALUE
指令的时候停止执行。
其他字节码指令
bytecode instruction
请参阅官方文档:
Python Bytecode Instructions
字节码中每个指令所占字节数是不一样的。
每条指令都有一个操作码 opcode
,它指明了虚拟机需要进行的操作,还有一个可选的参数,这个参数是一个整数。
操作码 opcode
是单字节的整数,所以最多有 256
个不同的操作码,尽管其中很多没有被用到。
每个操作码都有名字,在 dis
模块的 dis
函数的输出中可以见到,同时它们在 opcode
标准库模块中定义。
>>> from opcode import opname
>>> opname
['<0>', 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'DUP_TOP', 'DUP_TOP_TWO', '<6>', '<7>', '<8>', 'NOP', 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', '<13>', '<14>', 'UNARY_INVERT', 'BINARY_MATRIX_MULTIPLY', 'INPLACE_MATRIX_MULTIPLY', '<18>', 'BINARY_POWER', 'BINARY_MULTIPLY', '<21>', 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE', 'INPLACE_FLOOR_DIVIDE', 'INPLACE_TRUE_DIVIDE', '<30>', '<31>', '<32>', '<33>', '<34>', '<35>', '<36>', '<37>', '<38>', '<39>', '<40>', '<41>', '<42>', '<43>', '<44>', '<45>', '<46>', '<47>', '<48>', '<49>', 'GET_AITER', 'GET_ANEXT', 'BEFORE_ASYNC_WITH', '<53>', '<54>', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY', '<58>', 'INPLACE_MODULO', 'STORE_SUBSCR', 'DELETE_SUBSCR', 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR', 'BINARY_OR', 'INPLACE_POWER', 'GET_ITER', 'GET_YIELD_FROM_ITER', 'PRINT_EXPR', 'LOAD_BUILD_CLASS', 'YIELD_FROM', 'GET_AWAITABLE', '<74>', 'INPLACE_LSHIFT', 'INPLACE_RSHIFT', 'INPLACE_AND', 'INPLACE_XOR', 'INPLACE_OR', 'BREAK_LOOP', 'WITH_CLEANUP_START', 'WITH_CLEANUP_FINISH', 'RETURN_VALUE', 'IMPORT_STAR', 'SETUP_ANNOTATIONS', 'YIELD_VALUE', 'POP_BLOCK', 'END_FINALLY', 'POP_EXCEPT', 'STORE_NAME', 'DELETE_NAME', 'UNPACK_SEQUENCE', 'FOR_ITER', 'UNPACK_EX', 'STORE_ATTR', 'DELETE_ATTR', 'STORE_GLOBAL', 'DELETE_GLOBAL', '<99>', 'LOAD_CONST', 'LOAD_NAME', 'BUILD_TUPLE', 'BUILD_LIST', 'BUILD_SET', 'BUILD_MAP', 'LOAD_ATTR', 'COMPARE_OP', 'IMPORT_NAME', 'IMPORT_FROM', 'JUMP_FORWARD', 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'JUMP_ABSOLUTE', 'POP_JUMP_IF_FALSE', 'POP_JUMP_IF_TRUE', 'LOAD_GLOBAL', '<117>', '<118>', 'CONTINUE_LOOP', 'SETUP_LOOP', 'SETUP_EXCEPT', 'SETUP_FINALLY', '<123>', 'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST', '<127>', '<128>', '<129>', 'RAISE_VARARGS', 'CALL_FUNCTION', 'MAKE_FUNCTION', 'BUILD_SLICE', '<134>', 'LOAD_CLOSURE', 'LOAD_DEREF', 'STORE_DEREF', 'DELETE_DEREF', '<139>', '<140>', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_EX', 'SETUP_WITH', 'EXTENDED_ARG', 'LIST_APPEND', 'SET_ADD', 'MAP_ADD', 'LOAD_CLASSDEREF', 'BUILD_LIST_UNPACK', 'BUILD_MAP_UNPACK', 'BUILD_MAP_UNPACK_WITH_CALL', 'BUILD_TUPLE_UNPACK', 'BUILD_SET_UNPACK', 'SETUP_ASYNC_WITH', 'FORMAT_VALUE', 'BUILD_CONST_KEY_MAP', 'BUILD_STRING', 'BUILD_TUPLE_UNPACK_WITH_CALL', '<159>', 'LOAD_METHOD', 'CALL_METHOD', '<162>', '<163>', '<164>', '<165>', '<166>', '<167>', '<168>', '<169>', '<170>', '<171>', '<172>', '<173>', '<174>', '<175>', '<176>', '<177>', '<178>', '<179>', '<180>', '<181>', '<182>', '<183>', '<184>', '<185>', '<186>', '<187>', '<188>', '<189>', '<190>', '<191>', '<192>', '<193>', '<194>', '<195>', '<196>', '<197>', '<198>', '<199>', '<200>', '<201>', '<202>', '<203>', '<204>', '<205>', '<206>', '<207>', '<208>', '<209>', '<210>', '<211>', '<212>', '<213>', '<214>', '<215>', '<216>', '<217>', '<218>', '<219>', '<220>', '<221>', '<222>', '<223>', '<224>', '<225>', '<226>', '<227>', '<228>', '<229>', '<230>', '<231>', '<232>', '<233>', '<234>', '<235>', '<236>', '<237>', '<238>', '<239>', '<240>', '<241>', '<242>', '<243>', '<244>', '<245>', '<246>', '<247>', '<248>', '<249>', '<250>', '<251>', '<252>', '<253>', '<254>', '<255>']
不接收参数的操作码占用一个字节,而接收参数的操作码占用三个字节,其中第二、第三个字节按照小端序 little-endian order
存储参数。如果参数无法用两个字节表示,比如说大于 65535
,则会用到特殊的操作码 EXTENDED_ARG
。
co_cellvars
和co_freevars
:这两个属性用来实现嵌套函数的作用域。
co_cellvars
元组里面存储着所有被嵌套函数用到的变量名。
co_freevars
元组里面存储着所有被函数使用的在闭包作用域中定义的变量名。
这些元组内的变量名均按照字母表顺序排列。
如下例子所示,a
和c
是f
的cellvars
、是g
的freevars
。
def f(a, b):
c = 3
def g():
return a + c
return g
print(f.__code__.co_cellvars)
print(f.__code__.co_consts[2].co_freevars)
"""
('a', 'c')
('a', 'c')
"""
-
co_consts
:在函数中用到的所有常量,比如整数、字符串、布尔值等等。它会被LOAD_CONST
操作码使用,该操作码需要一个索引值作为参数,指明需要从co_consts
元组中加载哪一个元素。
co_consts
元组的第一个元素是函数的文档字符串docstring
,如果没有则为None
。 -
co_filename
:代码对象所在的文件名。
test.py
f = lambda: 0
print(f.__code__.co_filename)
"""
test.py
"""
co_firstlineno
:代码对象的第一行位于所在文件的行号。
# comment
f = lambda: 0
print(f.__code__.co_firstlineno)
"""
3
"""
-
co_flags
:这是一个整数,存放着函数的组合布尔标志位。
可以在inspect
模块的文档中查看这些标志位的具体含义:Code Objects Bit Flags -
co_lnotab
:这个属性是line number table
行号表的缩写。它以字节串bytes
的形式存储,每两个字节是一对,分别是co_code
字节串的偏移量和Python
行号的偏移量。
具体参阅:lnotab_notes.txt
-
co_kwonlyargcount
:存放强制关键字参数的个数。在Python 2
中则没有这个属性。
>>> def test(a, b, c, d=1, e=2, *args, f=3, g, h=4, **kwargs):
... print(a, b, c, d, e, f, g, h, args, kwargs)
...
>>> code_obj = test.__code__
>>> code_obj.co_kwonlyargcount
3
co_name
:是与代码对象关联的对象的名字。
>>> func = lambda: 0
>>> func.__code__.co_name
'<lambda>'
>>> def test(): pass
...
>>> test.__code__.co_name
'test'
co_names
:该属性是由字符串组成的元组,里面按照使用顺序存放了全局变量和被导入的名字。(注意官方文档的表格中说是局部变量的名字,实际上是不对的)
a = 1
def f(x):
x = a
print('hello')
print(f.__code__.co_names)
"""
('a', 'print')
"""
co_nlocals
:函数中局部变量的个数,相当于是co_varnames
的长度。co_stacksize
:一个整数,代表函数会使用的最大栈空间。co_varnames
:函数所有的局部变量名称(包括函数参数)组成的元组。
首先是位置参数、默认参数和强制关键字参数
然后是*args
和**kwargs
(如果有的话)
最后是按照第一次使用顺序排列的其他局部变量。
>>> def test(a, b, c, d=1, e=2, *args, f=3, g, h=4, **kwargs):
... print(a, b, c, d, e, f, g, h, args, kwargs)
... x = 666
...
>>> code_obj = test.__code__
>>> code_obj.co_varnames
('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'args', 'kwargs', 'x')
完成于 2019.02.04