如何在python中创建代码对象?

2024-05-11 18:18:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我想用函数types.CodeType()创建一个新的代码对象。
几乎没有关于这方面的文献,现有的文献说“不适合心脏虚弱者”
告诉我需要什么,并提供有关传递给types.CodeType的每个参数的一些信息,
可能会贴出一个例子。

注意
在正常的用例中,您只需要内置函数compile()
仅当要创建新指令时,才应使用types.CodeType(),这些指令在编写普通源代码时无法获取,并且需要直接访问字节码。


Tags: 对象函数代码信息参数指令用例文献
2条回答

–––––––––––
免责声明 此答案中的文档不是官方的,可能不正确。

此答案仅对Python3.x版有效

———————————————————

要创建代码对象,必须将以下参数传递给函数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
        )

现在我将试着解释每一个论点的含义。

参数计数
要传递给函数的参数数(*不包括args和**kwargs)。

kwonlyargcount
keyword-only arguments的数目。

非本地
局部变量的数量,
即除全局名称外的所有变量和参数(包括参数和**kwargs)。

堆叠大小 代码所需的堆栈(虚拟机堆栈)数量,
如果您想了解它是如何工作的,请参见官方的Documentation

标志
表示代码对象的位图:
1–>;代码已优化
2–>;newlocals:有一个新的本地命名空间(例如一个函数)
4–>;代码接受任意数量的位置参数(*使用args)
8–>;代码接受任意数量的关键字参数(使用kwargs)
32–>;代码是生成器

othes标志在较旧的python版本中使用,或者被激活以说明从未来导入的内容

代码字符串
表示字节码指令的字节序列 如果您想要更好的理解,请参见Documentation(同上)

常量
包含字节码使用的文本的元组(例如,预先计算的数字、元组和字符串)

姓名
包含字节码使用的名称的元组
这些名称是全局变量、函数和类,也可以是从对象加载的属性

变量名
包含字节码使用的本地名称的元组(首先是参数,然后是本地变量)

文件名
它是编译代码的文件名。
它可以是你想要的任何东西,你可以在这件事上撒谎。;)

姓名
它给出了函数的名称。 这也可以是你想要的任何东西,但是要小心:
这是回溯中显示的名称,如果名称不清楚,则回溯可能不清楚,
想想lambdas有多烦人。

第一行编号 函数的第一行(如果编译了源代码,则用于调试)

总数
将字节码偏移量与行号相关联的字节映射。
(我认为这也是为了调试的目的,关于这方面的文档很少)

自由变量
包含自由变量名称的元组。
自由变量是在定义代码对象的命名空间中声明的变量, 它们在声明嵌套函数时使用;
这不会在模块级发生,因为在这种情况下,自由变量也是全局变量。

单元变量
包含嵌套函数引用的局部变量名的元组。

–––––––––––
示例
下面的例子应该澄清上面所说的意思。

注意:在上面提到的finished code objects属性的前缀是
函数的可执行体存储在

–––––––––––
第一个示例

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))
  1. 传递给此函数的参数有两个(“a”、“b”)

  2. 此函数有两个参数(“a”、“b”)和三个局部变量(“k”、“w”、“p”)

  3. 通过t分解函数ecode我们得到:

    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:  []
    

    正如您可以注意到chile正在执行该函数一样,堆栈中的元素永远不会超过三个(在本例中,元组计算为其长度)

  4. flag的值是dec67=bin10000011=bin1000000+10+1=dec64+2+1,因此我们知道

    • 代码是优化的(因为大多数自动生成的代码是优化的)
    • 执行函数字节码本地命名空间更改时
    • 64岁?其实我不知道这是什么意思
  5. 函数中使用的唯一全局名称是“c”,它存储在co_names中

  6. 我们使用的每一个显式文字都存储在co consts中:

    • None是函数的返回值
    • 我们显式地把数字10赋给w
    • 我们显式地将(1,'2',3)赋给p
    • 如果元组是常量,则该元组的每个元素都是常量,因此1、“2”、3是常量

–––––––––––
第二个例子

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-u名称:
“FunctionVar”位于F function的命名空间中,其中G是创建的,
“ModuleVar”是一个模块变量,因此它被视为全局变量。

下面三行是关于F代码的co廑cellvars、co廑freevars和co廑names属性:
“FunctionVar”在G嵌套函数中被引用,因此它被标记为cellvar,
“ModuleVar”在创建F的命名空间中,但它是一个模块变量,
因此它没有标记为freevar,但在全局名称中找到。
此外,内置函数print在names和F中使用的所有属性名中都有标记

–––––––––––
第三个例子

这是一个工作代码对象初始化,
这是不实用的,但是你可以用这个函数做任何你想做的事情。

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

代码类型构造函数的示例用法可以在标准库中找到,特别是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__成员不是只读的,因此我们可以执行modulefinder所做的操作:

>>> 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)。

更新:VictorStinner在Python3.8的CodeType类中添加了一个新方法“replace”,这大大简化了这种情况。这样做是为了消除将来的兼容性问题,因为3.8还在“co_argcount”之后的调用列表中添加了一个新的“co_posonlyargcount”参数,因此,如果参数列表再次更改,至少3.8和更高版本的代码将在将来得到一定程度的验证。

>>> x.__code__ = x.__code__.replace(co_name='MyNewCodeName')

相关问题 更多 >