根据官方文档的解释Code Objects,翻译过来大意是:代码对象是CPython实现的底层细节。每一个都代表一段尚未绑定到函数中的可执行代码。
code对象概览
方法的__code__
属性
>>> def foo():
... print("我是foo")
...
>>> foo.__code__
<code object foo at 0x7f7dd16693a0, file "<stdin>", line 1>
使用compile
方法
>>> code_str = """
... def foo():
... print("我是foo")
... """
>>> compile(code_str, '<string>', 'exec')
<code object <module> at 0x7f7dd176b240, file "<string>", line 2>
运行code
对象
code
对象可以通过exec
方法直接运行
>>> def foo():
... print("我是方法foo")
...
>>>
>>> code_obj1 = foo.__code__
>>>
>>> code_str = """
... def foo():
... print("我是模块foo")
... foo()
... """
>>>
>>> code_obj2 = compile(code_str, '<string>', 'exec')
>>>
>>> exec(code_obj1)
我是方法foo
>>> exec(code_obj2)
我是模块foo
替换__code__
属性?
既然__code__
对象可以单独运行,那么可以人为替换方法的code
对象吗?
>>> def foo():
... print("我是foo")
...
>>>
>>> def foo2():
... print("我是foo2")
...
>>>
>>> foo2.__code__ = foo.__code__
>>>
>>> foo2()
我是foo
code对象属性详解
使用dir
方法,并排除掉从object
继承的属性:
>>> def foo():
... print("我是方法foo")
...
>>>
>>> code_obj = foo.__code__
>>>
>>> set(dir(code_obj)) - set(dir(object))
{'co_filename', 'co_freevars', 'co_posonlyargcount', 'co_code', 'co_consts', 'replace', 'co_firstlineno', 'co_names', 'co_argcount', 'co_varnames', 'co_cellvars', 'co_name', 'co_nlocals', 'co_lnotab', 'co_kwonlyargcount', 'co_flags', 'co_stacksize'}
如下表格所示
Attribute | Description |
---|---|
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_posonlyargcount | number of positional only arguments |
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_code
代码的二进制bytecode
,也就是字节码。
>>> def foo(a, b=1, *args, **kwargs):
... print("a + b =", a + b)
...
>>>
>>> code_obj = foo.__code__
>>>
>>> print(code_obj.co_code)
b't\x00d\x01|\x00|\x01\x17\x00\x83\x02\x01\x00d\x00S\x00'
co_code
代表的字节码看不懂,可以使用dis
转换为人类可读的形式:
>>> import dis
>>> dis.dis(foo)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('a + b =')
4 LOAD_FAST 0 (a)
6 LOAD_FAST 1 (b)
8 BINARY_ADD
10 CALL_FUNCTION 2
12 POP_TOP
14 LOAD_CONST 0 (None)
16 RETURN_VALUE
>>>
co_name、co_filename、co_lnotab
- co_name:当前代码被定义的名字,一般就是方法(模块)名
- co_filename:创建这段代码的文件名
- co_lnotab:是行号到字节码索引的编码映射,具体怎么压缩映射的可以看这里lnotab_notes.txt
def foo(a, b=1, *args, **kwargs):
print("a + b =", a + b)
code_obj = foo.__code__
print(code_obj.co_name)
print(code_obj.co_filename)
print(code_obj.co_lnotab)
# 输出
# foo
# /Users/dengrunting/data/code/demos/code_obj/demo_05.py
# b'\x00\x01'
co_flags、co_stacksize
- co_flags:是一个
bit map
,编译器编译的时候会根据co_flags
的值,可能会有不同的处理方式,具体可以看这里Code Objects Bit Flags - co_stacksize:虚拟机需要的栈空间大小
def foo(a, b=1, *args, **kwargs):
print("a + b =", a + b)
code_obj = foo.__code__
print(code_obj.co_flags)
print(code_obj.co_stacksize)
# 79
# 4
co_argcount、co_posonlyargcount、co_kwonlyargcount
- co_argcount:参数个数,不包含只能位置传参的参数(示例代码中就是参数
a
,b
,c
,所以值是3) - co_posonlyargcount:只能通过位置传参的参数个数(示例代码中就是参数
a
,所以值是1) - co_kwonlyargcount:只能通过关键字传参的参数个数(示例代码中就是参数
d
,e
,所以值是2)
提示:/
之前的参数只能位置传参,*
之后的参数只能关键字传参
def foo(a, /, b, c, *, d, e=10, **kwargs):
print("a + b =", a + b)
code_obj = foo.__code__
print(code_obj.co_argcount)
print(code_obj.co_posonlyargcount)
print(code_obj.co_kwonlyargcount)
# 输出:
# 3
# 1
# 2
co_nlocals、co_varnames
- co_varnames:局部变量个数
- co_varnames:局部变量元祖
def foo(a, b, **kwargs):
print("a + b =", a + b)
x = 20
code_obj = foo.__code__
print("co_nlocals", code_obj.co_nlocals)
print("co_varnames", code_obj.co_varnames)
# 输出
# co_nlocals 4
# co_varnames ('a', 'b', 'kwargs', 'x')
co_freevars、co_cellvars、co_consts
- co_freevars:闭包引用的变量名元祖(
bar
的co_freevars
为a
、b
,是引用的foo
中的,而foo
的co_freevars
为空元祖) - co_cellvars:被所包含的作用域引用的变量名元祖(
foo
中的a
、b
被bar
引用了,s
被D
引用了,所以结果为('a', 'b', 's')
) - co_consts:字节码中用到的常量
n = 30
def foo(a, b, **kwargs):
s = a + b
def bar():
return a * b + n
class D:
z = s + 1
print("co_freevars of bar", bar.__code__.co_freevars)
return s
code_obj = foo.__code__
foo(1, 2)
print("co_freevars of foo", code_obj.co_freevars)
print("co_cellvars", code_obj.co_cellvars)
print("co_names", code_obj.co_names)
print("co_consts", code_obj.co_consts)
# 输出
# co_freevars of bar ('a', 'b')
# co_freevars of foo ()
# co_cellvars ('a', 'b', 's')
# co_names ('print', '__code__', 'co_freevars')
# co_consts (None, <code object bar at 0x7f86dfdd17c0, file "/Users/dengrunting/data/code/demos/code_obj/demo_05.py", line 4>, 'foo.<locals>.bar', <code object D at 0x7f86dfdd1870, file "/Users/dengrunting/data/code/demos/code_obj/demo_05.py", line 9>, 'D', 'co_freevars of bar')
co_names
解释不是很清楚,但是通过dis.dis(foo)
可以看出,co_names
的值跟LOAD_ATTR
和LOAD_GLOBAL
有关(指令含义局可以看这里),所以基本可以理解为全局变量名和对象属性名的元祖。
def foo(a, b, **kwargs):
class D:
pass
print("co_freevars of bar", D.x.y.z)
return a + b
code_obj = foo.__code__
print("co_names", code_obj.co_names)
# 输出
# co_names ('print', 'x', 'y', 'z')
通过dis
查看可读的字节码:
import dis
print(dis.dis(foo))
结果如下:
2 0 LOAD_BUILD_CLASS
2 LOAD_CONST 1 (<code object D at 0x7f8836d727c0, file "/Users/dengrunting/data/code/demos/code_obj/demo_06.py", line 2>)
4 LOAD_CONST 2 ('D')
6 MAKE_FUNCTION 0
8 LOAD_CONST 2 ('D')
10 CALL_FUNCTION 2
12 STORE_FAST 3 (D)
5 14 LOAD_GLOBAL 0 (print)
16 LOAD_CONST 3 ('co_freevars of bar')
18 LOAD_FAST 3 (D)
20 LOAD_ATTR 1 (x)
22 LOAD_ATTR 2 (y)
24 LOAD_ATTR 3 (z)
26 CALL_FUNCTION 2
28 POP_TOP
6 30 LOAD_FAST 0 (a)
32 LOAD_FAST 1 (b)
34 BINARY_ADD
36 RETURN_VALUE
Disassembly of <code object D at 0x7f8836d727c0, file "/Users/dengrunting/data/code/demos/code_obj/demo_06.py", line 2>:
2 0 LOAD_NAME 0 (__name__)
2 STORE_NAME 1 (__module__)
4 LOAD_CONST 0 ('foo.<locals>.D')
6 STORE_NAME 2 (__qualname__)
3 8 LOAD_CONST 1 (None)
10 RETURN_VALUE
None