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

Alb*_*lla 15 python bytecode python-3.x

我想用函数types.CodeType()创建一个新的代码对象.
几乎没有关于这个的文档和现有的文档说"不是为了胆小的"
告诉我我需要什么,并给我一些关于传递给types.CodeType的每个参数的信息,
可能发布一个例子.

注意:
在正常使用情况下,您只需要内置函数compile()
只有当您想要创建无法编写正常源代码并且需要直接访问字节码的新指令时,才应使用types.CodeType().

Alb*_*lla 37

-----------
免责声明:
本答复中的文档不是官方文档,可能不正确.

这个答案仅对python版本3.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
        )
Run Code Online (Sandbox Code Playgroud)

现在我将尝试解释每个论点的含义.

argcount
要传递给函数的参数数量(不包括*args和**kwargs).

kwonlyargcount
只有关键字的参数.

nlocals
局部变量的数量,
即除全局名称外的所有变量和参数(包括*args和**kwargs).

stacksize 代码所需的堆栈(虚拟机堆栈)数量,
如果您想了解它的工作原理,请参阅官方文档.

flags
一个说明代码对象的位图:
1 - >代码优化
2 - > newlocals:有一个新的本地命名空间(例如一个函数)
4 - >代码接受任意数量的位置参数(*args是使用)
8 - >代码接受任意数量的keyworded参数(使用*kwargs)
32 - >代码是生成器

othes标志用于较旧的python版本或被激活以说明从__ future __ 导入的内容

codestring 如果想要更好地理解,
表示字节码指令的字节序列
,请参阅文档(与上面相同)

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

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

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

filename
这是编译代码的文件名.
它可以是你想要的任何东西,你可以自由地撒谎.;)

name
它给出了函数的名称.此外,这可以是你想要的任何东西,但要小心:
这是追溯中显示的名称,如果名称不清楚,追溯可能不清楚,
只要想想lambdas如何烦人.

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

lnotab
将字节码偏移与行号相关联的字节映射.
(我认为这也是出于调试目的,关于此的文档很少)

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

cellvars
包含嵌套函数引用的局部变量名称的元组.

------------
示例:
以下示例应阐明上述内容的含义.

注意:在完成的代码对象中,上面提到的属性具有co_前缀,
而函数将其可执行主体存储在__code__属性中

------------
第一个例子

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)
Run Code Online (Sandbox Code Playgroud)

输出:

2
5 ('a', 'b', 'k', 'w', 'p')
3
67
('c' ,)
(None, 10, 1, 'two'. 3, (1, 'two', 3))
Run Code Online (Sandbox Code Playgroud)
  1. 有两个参数传递给这个函数("a","b")

  2. 这个函数有两个参数("a","b")和三个局部变量("k","w","p")

  3. 反汇编我们得到的函数字节码:

    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:  []
    
    Run Code Online (Sandbox Code Playgroud)

    你可以注意到chile正在执行这个函数,我们在堆栈中从来没有超过三个元素(在这种情况下,元组计算为它的长度)

  4. flag的值是dec 67 = bin 1000011 = bin 1000000 +10 +1 = dec 64 +2 +1,所以我们理解

    • 代码已经过优化(因为大多数自动生成的代码都是)
    • 执行函数字节码时本地命名空间发生变化
    • 64?其实我不知道它的含义是什么
  5. 函数中使用的唯一全局名称是"c",它存储在co_names中

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

    • None是函数的返回值
    • 我们明确地将数字10分配给w
    • 我们明确地将(1,'two',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)
Run Code Online (Sandbox Code Playgroud)

输出:

('FunctionVar',)
('ModuleVar',)
('FunctionVar',)
()
('print', '__code__', 'co_freevars', 'co_names', 'ModuleVar')
Run Code Online (Sandbox Code Playgroud)

输出的含义是这样的:

执行F时会打印第一行和第二行,因此它们显示G代码的co_freevars和co_names:
"FunctionVar"位于F函数的命名空间中,其中G创建,
"ModuleVar"则是模块变量,因此它被认为是全球化.

以下三行是关于F代码的co_cellvars,co_freevars和co_names属性:
在G嵌套函数中引用"FunctionVar",因此它被标记为cellvar,
"ModuleVar"在创建F的命名空间中,但它是一个模块变量,
因此它没有标记为freevar,但它在全局名称中找到.
内置函数print也用名字标记,并在F中使用所有属性的名称.

------------
第3个例子

这是一个有效的代码对象初始化,
这是无用的,但你可以用这个函数做你想做的一切.

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"
Run Code Online (Sandbox Code Playgroud)

输出:

5
6
Run Code Online (Sandbox Code Playgroud)

  • [Python 核心](https://github.com/python/cpython/blob/7ed7aead9503102d2ed316175f198104e0cd674c/Include/code.h) 适合任何想要深入挖掘的人。 (2认同)

Eri*_*ren 5

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!'
Run Code Online (Sandbox Code Playgroud)

但是,等等,函数的__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
Run Code Online (Sandbox Code Playgroud)

在这个例子中需要注意的是,回溯使用的是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')
Run Code Online (Sandbox Code Playgroud)