Python 中的代码对象 code object 与 __code__ 属性

Python 中的代码对象 code object__code__ 属性



0. 参考资料


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_cellvarsco_freevars:这两个属性用来实现嵌套函数的作用域。
    co_cellvars 元组里面存储着所有被嵌套函数用到的变量名。
    co_freevars 元组里面存储着所有被函数使用的在闭包作用域中定义的变量名。
    这些元组内的变量名均按照字母表顺序排列。
    如下例子所示,acfcellvars、是 gfreevars
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

猜你喜欢

转载自blog.csdn.net/jpch89/article/details/86764245