我想用函数 types.CodeType() 创建一个新的代码对象。
几乎没有关于此的文档,现有的文档说“不适合胆小的人”
告诉我我需要什么,并给我一些关于传递给 types.CodeType 的每个参数的信息,
可能会发布一个示例。
注意:
在正常用例中,您只需要内置函数 compile(
) 只有当您想创建无法通过编写普通源代码获得的新指令并且需要直接访问字节码时,您才应该使用 types.CodeType()。
我想用函数 types.CodeType() 创建一个新的代码对象。
几乎没有关于此的文档,现有的文档说“不适合胆小的人”
告诉我我需要什么,并给我一些关于传递给 types.CodeType 的每个参数的信息,
可能会发布一个示例。
注意:
在正常用例中,您只需要内置函数 compile(
) 只有当您想创建无法通过编写普通源代码获得的新指令并且需要直接访问字节码时,您才应该使用 types.CodeType()。
–––––––––––<br>
免责声明:
此答案中的文档不是官方的,可能不正确。
此答案仅对 python 版本 3.x 有效
–––––––––––</p>
为了创建代码对象,您必须将以下参数传递给函数 CodeType():
CodeType(
argcount, # integer
kwonlyargcount, # integer
nlocals, # integer
stacksize, # integer
flags, # integer
codestring, # bytes
consts, # tuple
names, # tuple
varnames, # tuple
filename, # string
name, # string
firstlineno, # integer
lnotab, # bytes
freevars, # tuple
cellvars # tuple
)
现在我将尝试解释每个参数的含义。
argcount
要传递给函数的参数数量(不包括 *args 和 **kwargs)。
kwonlyargcount仅关键字参数的
数量。
nlocals
局部变量的数量,
即除全局名称外的所有变量和参数(包括 *args 和 **kwargs)。
stacksize
代码所需的栈(虚拟机栈)的数量,
如果想了解它是如何工作的,请看官方文档。
flags
说明代码对象的位图:
1 –> 代码已优化
2 –> newlocals:有一个新的本地命名空间(例如函数)
4 –> 代码接受任意数量的位置参数(*args 是used)
8 –> 代码接受任意数量的关键字参数(使用 *kwargs)
32 –> 代码是生成器
其他标志用于较旧的 python 版本或被激活以说明从 __未来__ 导入的内容
codestring
表示字节码指令的字节序列,
如果您想更好地理解,请参阅文档(同上)
consts
包含字节码使用的文字的元组(例如预先计算的数字、元组和字符串)
names
包含字节码使用的名称的元组,
这些名称是全局变量、函数和类,或者也是从对象加载的属性
varnames
包含字节码使用的本地名称的元组(首先是参数,然后是局部变量)
文件名
它是编译代码的文件名。
它可以是你想要的任何东西,你可以自由地对此撒谎。;)
name
它给出函数的名称。这也可以是任何你想要的,但要小心:
这是回溯中显示的名称,如果名称不清楚,回溯可能不清楚,
想想 lambdas 是多么烦人。
firstlineno
函数的第一行(如果您编译了源代码,则用于调试目的)
lnotab
将字节码偏移量与行号相关联的字节映射。
(我认为这也是出于调试目的,关于此的文档很少)
freevars
包含自由变量名称的元组。
自由变量是在定义代码对象的命名空间中声明的变量,它们在声明嵌套函数时使用;
这不会发生在模块级别,因为在这种情况下,自由变量也是全局变量。
cellvars
包含嵌套函数引用的局部变量名称的元组。
––––––––––––<br>
示例:
以下示例应阐明上述内容的含义。
注意:在上述完成的代码对象中,属性具有co_前缀,
并且函数将其可执行体存储在__code__属性中
––––––––––––<br> 第一个例子
def F(a,b):
global c
k=a*c
w=10
p=(1,"two",3)
print(F.__code__.co_argcount)
print(F.__code__.co_nlocals , F.__code__.co_varnames)
print(F.__code__.co_stacksize)
print(F.__code__.co_flags)
print(F.__code__.co_names)
print(F.__code__.co_consts)
输出:
2
5 ('a', 'b', 'k', 'w', 'p')
3
67
('c' ,)
(None, 10, 1, 'two'. 3, (1, 'two', 3))
有两个参数传递给这个函数(“a”,“b”)
这个函数有两个参数(“a”,“b”)和三个局部变量(“k”,“w”,“p”)
反汇编我们得到的函数字节码:
3 0 LOAD_FAST 0 (a) #stack: ["a"]
3 LOAD_GLOBAL 0 (c) #stack: ["a","c"]
6 BINARY_MULTIPLY #stack: [result of a*c]
7 STORE_FAST 2 (k) #stack: []
4 10 LOAD_CONST 1 (10) #stack: [10]
13 STORE_FAST 3 (w) #stack: []
5 16 LOAD_CONST 5 ((1, 'two', 3)) #stack: [(1,"two",3)]
19 STORE_FAST 4 (p) #stack: []
22 LOAD_CONST 0 (None) #stack: [None]
25 RETURN_VALUE #stack: []
正如你可以注意到智利执行的函数我们在堆栈中永远不会有超过三个元素(在这种情况下,元组算作它的长度)
flag 的值是dec 67 = bin 1000011 = bin 1000000 +10 +1 = dec 64 +2 +1 ,所以我们明白
函数中使用的唯一全局名称是 "c" ,它存储在 co_names
我们使用的每个显式文字都存储在 co_consts 中:
––––––––––––<br> 第二个例子
ModuleVar="hi"
def F():
FunctionVar=106
UnusedVar=ModuleVar
def G():
return (FunctionVar,ModuleVar)
print(G.__code__.co_freevars)
print(G.__code__.co_names)
F()
print(F.__code__.co_cellvars)
print(F.__code__.co_freevars)
print(F.__code__.co_names)
输出:
('FunctionVar',)
('ModuleVar',)
('FunctionVar',)
()
('print', '__code__', 'co_freevars', 'co_names', 'ModuleVar')
输出的含义是这样的:
执行 F 时打印第一行和第二行,因此它们显示 G 代码的 co_freevars 和 co_names:
“FunctionVar”在 F 函数的命名空间中,G 是在其中创建的,
“ModuleVar”是一个模块变量,所以它被认为是作为全球。
以下三行是关于 F 代码的 co_cellvars、co_freevars 和 co_names 属性:
“FunctionVar”在 G 嵌套函数中被引用,因此标记为 cellvar,
“ModuleVar”在创建 F 的命名空间中,但它是一个模块变量,
所以它没有被标记为freevar,但它可以在全局名称中找到。
内置函数 print 也被标记在 names 中,以及 F 中使用的所有属性的名称。
––––––––––––<br> 第三个例子
这是一个有效的代码对象初始化,
这是无用的,但你可以用这个函数做任何你想做的事情。
MyCode= CodeType(
0,
0,
0,
3,
64,
bytes([101, 0, 0, #Load print function
101, 1, 0, #Load name 'a'
101, 2, 0, #Load name 'b'
23, #Take first two stack elements and store their sum
131, 1, 0, #Call first element in the stack with one positional argument
1, #Pop top of stack
101, 0, 0, #Load print function
101, 1, 0, #Load name 'a'
101, 2, 0, #Load name 'b'
20, #Take first two stack elements and store their product
131, 1, 0, #Call first element in the stack with one positional argument
1, #Pop top of stack
100, 0, 0, #Load constant None
83]), #Return top of stack
(None,),
('print', 'a', 'b'),
(),
'PersonalCodeObject',
'MyCode',
1,
bytes([14,1]),
(),
() )
a=2
b=3
exec(MyCode) # code prints the sum and the product of "a" and "b"
输出:
5
6
CodeType 构造函数的示例用法可以在标准库中找到,特别是 Lib/modulefinder.py。如果你看那里,你会看到它被用来重新定义文件中所有代码对象的只读co_filename
属性。
我最近遇到了一个类似的用例,我有一个函数工厂,但是生成的函数在回溯中总是有“通用”名称,所以我不得不重新生成代码对象以包含所需的名称。
>>> def x(): raise NotImplementedError
...
>>> x.__name__
'x'
>>> x.__name__ = 'y'
>>> x.__name__
'y'
>>> x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in x
NotImplementedError
>>> x.__code__.co_name
'x'
>>> x.__code__.__name__ = 'y'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: readonly attribute
>>> 'Gah!'
'Gah!'
但是,等等,函数的__code__
成员不是只读的,所以我们可以做模块查找器所做的事情:
>>> from types import CodeType
>>> co = x.__code__
>>> x.__code__ = CodeType(co.co_argcount, co.co_kwonlyargcount,
co.co_nlocals, co.co_stacksize, co.co_flags,
co.co_code, co.co_consts, co.co_names,
co.co_varnames, co.co_filename,
'MyNewCodeName',
co.co_firstlineno, co.co_lnotab, co.co_freevars,
co.co_cellvars)
>>> x.__code__.co_name
'MyNewCodeName'
>>> x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in MyNewCodeName
NotImplementedError
此示例中要注意的是,在堆栈跟踪中生成值时,回溯使用的是co_name
属性,而不是属性。func.__name__
还要注意:以上是 Python 3,为了使其与 Python 2 兼容,只需省略构造函数的第二个参数 ( co_kwonlyargcount
)。
更新:Victor Stinner 在 Python 3.8 的 CodeType 类中添加了一个新方法“replace”,这大大简化了这种情况。这样做是为了消除未来的兼容性问题,因为 3.8 还在 'co_argcount' 之后的调用列表中添加了一个新的 'co_posonlyargcount' 参数,因此如果参数列表再次更改,至少您的 3.8 和更高版本的代码将在一定程度上得到未来的证明。
>>> x.__code__ = x.__code__.replace(co_name='MyNewCodeName')