记录一下关于 python CodeObject 的一些知识点
什么是 Code Object
Code Object 是 Python 内部用来表示编译后代码的数据结构. 当 Python 解释器将源代码编译成字节码时,会创建 Code Object 来存储这些字节码以及相关的元数据. 每个函数、类、模块都会生成对应的 Code Object.
访问函数的__code__属性即可获取到对应的CodeObject对象
def func(a):
print(a)
print(func.__code__)
# <code object func at 0x104836f10, file "/Users/asteriax/Desktop/codes/temp/2.py", line 1>
Code Object 的属性
查看某个函数对应的Code Object的属性
def func(a):
print(a + "sth")
for attr in dir(func.__code__):
if attr.startswith('co_'):
print(f"{attr}:\t{getattr(func.__code__, attr)}")
运行,即可看到一个类似这样的输出
co_argcount: 1
co_cellvars: ()
co_code: b'\x95\x00[\x01\x00\x00\x00\x00\x00\x00\x00\x00U\x00S\x01-\x00\x00\x005\x01\x00\x00\x00\x00\x00\x00 \x00g\x00'
co_consts: (None, 'sth')
co_exceptiontable: b''
co_filename: /Users/asteriax/Desktop/codes/temp/1.py
co_firstlineno: 1
co_flags: 3
co_freevars: ()
co_kwonlyargcount: 0
co_lines: <built-in method co_lines of code object at 0x1056e2f10>
co_lnotab: b'\x02\x01'
co_name: func
co_names: ('print',)
co_nlocals: 1
co_positions: <built-in method co_positions of code object at 0x1056e2f10>
co_posonlyargcount: 0
co_qualname: func
co_stacksize: 4
co_varnames: ('a',)
Code Object各个属性的含义
co_argcount
表示函数的位置参数数量
这里顺便介绍一下Python中的位置参数和关键字参数的概念
位置参数(Positional Arguments):调用函数时,按照参数在定义时的位置顺序传递的参数
关键字参数(Keyword Arguments):调用函数时,通过“参数名=值”的方式传递的参数,顺序可以与定义时不同
例如:
def func(a, b, c, /, d, *, e, f=6, **kwargs):
"""
a, b, c: 位置参数
/: 仅限位置参数分隔符
d: 位置或关键字参数
*: 仅限关键字参数分隔符
e: 仅限关键字参数
f: 仅限关键字参数(有默认值)
**kwargs: 可变关键字参数
"""
pass
codeobj = func.__code__
print(f"{codeobj.co_argcount}") # 4 (a,b,c,d)
print(f"{codeobj.co_posonlyargcount}") # 3 (a,b,c)
print(f"{codeobj.co_kwonlyargcount}") # 2 (e,f)
co_posonlyargcount
表示仅限位置参数的数量
co_kwonlyargcount
表示仅限关键字参数的数量
co_varnames
包含所有局部变量和参数的名字,按顺序排列
co_consts
函数体中用到的所有常量组成的元组,包括字面量、字符串、None等
例如,如果函数体中出现 print(a + “sth”) ,使用了 “sth” 这个字符串常量,“sth”对应地就会出现在 co_consts 里
co_names
函数体中用到的所有全局变量名、函数名等组成的元组
co_code
字节码指令的二进制表示
co_filename
文件名
co_name
代码对象的名字(如函数名) 如果有一个函数 def foo():,那么他对应的 co_name 就是字符串 “foo”
co_firstlineno
代码对象在源文件中的起始行号 例如,如果函数在文件的第 1 行开始,co_firstlineno 就是 1
co_flags
标志位,描述函数的特性(如是否有 *args、**kwargs 等)
co_stacksize
表示执行代码时需要的最大栈大小
co_freevars
自由变量(在当前作用域外部定义的、但被当前作用域引用的变量)的名称
co_cellvars
函数内被嵌套作用域引用的局部变量的名称(如果一个函数内有嵌套函数并且引用了外部函数的局部变量,那么这些外部变量会出现在 co_cellvars 中)
co_lnotab
行号表,用于字节码和源码行号的映射
co_exceptiontable
异常处理表
co_qualname
函数的完整限定名称 (包括模块和类(如果是类方法)路径的名称)
例如,如果一个函数定义在 xxx 模块中的 yyy 类下,co_qualname 会返回 “xxx.yyy.foo”
co_nlocals
函数中使用的局部变量和形参的数量
通过修改 Code Object 改变函数行为
函数替换
def foo():
a = 1
b = 2
print(a+b)
def bar():
pass
hack_co_keys = ["co_code", "co_consts", "co_names", "co_stacksize", "co_nlocals", "co_varnames"]
hack_co_attrs = { key: getattr(foo.__code__, key) for key in hack_co_keys }
bar.__code__ = bar.__code__.replace(**hack_co_attrs)
bar()
# output: 3
修改常量
def func():
with open('test', 'r') as f:
print(f.read())
func()
print(func.__code__.co_consts)
dict = {"co_consts": (None, 'flag', 'r')}
func.__code__ = func.__code__.replace(**dict)
func()
# output:
# teststring
# (None, 'test', 'r')
# flag{thisisflag}
