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

时间:2013-04-17 15:40:07

标签: python python-3.x bytecode

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

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

2 个答案:

答案 0 :(得分:35)

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

此答案仅适用于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
        )

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

<强> argcount
要传递给函数的参数数量(不包括* args和** kwargs)。

<强> kwonlyargcount
keyword-only arguments的数量。

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

<强> STACKSIZE 代码所需的堆栈(虚拟机堆栈)数量,
如果您想了解它的工作原理,请参阅官方Documentation

<强>标志
一个描述代码对象的位图:
1 - &gt;代码优化了 2 - &gt; newlocals:有一个新的本地命名空间(例如一个函数)
4 - &gt;代码接受任意数量的位置参数(使用* args)
8 - &gt;代码接受任意数量的keyworded参数(使用* kwargs)
32 - &gt;代码是生成器

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

<强>代码串
表示字节码指令的字节序列
如果您想要更好地理解,请参阅Documentation(与上述相同)

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

<强>名称
包含字节码
使用的名称的元组 这些名称是全局变量,函数和类,或者也是从对象

加载的属性

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

<强>文件名
它是编译代码的文件名 它可以是你想要的任何东西,你可以自由地撒谎。 ;)

命名
它给出了函数的名称。 这也可以是你想要的任何东西,但要小心:
这是追溯中显示的名称,如果名称不清楚,追溯可能不清楚,
想想lambda如何烦恼。

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

输出:

2
5 ('a', 'b', 'k', 'w', 'p')
3
67
('c' ,)
(None, 10, 1, 'two'. 3, (1, 'two', 3))
  1. 有两个参数传递给此函数(&#34; a&#34;,&#34; b&#34;)

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

  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. 标记的值是 dec 67 = bin 1000011 = bin 1000000 +10 +1 = dec 64 +2 +1,所以我们理解

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

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

    • 无是函数的返回值
    • 我们明确地将数字10分配给w
    • 我们明确指定(1,&#39;两个&#39;,3)到p
    • 如果元组是常量,那个元组的每个元素都是常量,所以1,&#34; 2&#34; 3是常量<​​/ li>
  7. ------------
    第二个例子

    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:
    &#34; FunctionVar&#34;在F函数的名称空间中,其中G被创建,
    &#34; ModuleVar&#34;而是一个模块变量,因此它被认为是全局的。

    以下三行是关于F代码的co_cellvars,co_freevars和co_names属性:
    &#34; FunctionVar&#34;在G嵌套函数中引用,因此它被标记为cellvar,
    &#34; ModuleVar&#34;在创建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"
    

    输出:

    5
    6
    

答案 1 :(得分:2)

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