21

我想用函数 types.CodeType() 创建一个新的代码对象。
几乎没有关于此的文档,现有的文档说“不适合胆小的人”
告诉我我需要什么,并给我一些关于传递给 types.CodeType 的每个参数的信息,
可能会发布一个示例。

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

4

2 回答 2

43

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

    正如你可以注意到智利执行的函数我们在堆栈中永远不会有超过三个元素(在这种情况下,元组算作它的长度)

  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,"two",3 是常数

––––––––––––<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
于 2013-04-20T17:26:21.850 回答
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!'

但是,等等,函数的__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')
于 2016-10-16T00:31:31.443 回答