• Python函数属性和PyCodeObject


    函数属性

    python中的函数是一种对象,它有属于对象的属性。除此之外,函数还可以自定义自己的属性。注意,属性是和对象相关的,和作用域无关。

    自定义属性

    自定义函数自己的属性方式很简单。假设函数名称为myfunc,那么为这个函数添加一个属性var1:

    myfunc.var1="abc"
    

    那么这个属性var1就像是全局变量一样被访问、修改。但它并不是全局变量。

    可以跨模块自定义函数的属性。例如,在b.py中有一个函数b_func(),然后在a.py中导入这个b.py模块,可以直接在a.py中设置并访问来自b.py中的b_func()的属性。

    import b
    b.b_func.var1="hello"
    print(b.b_func.var1)  # 输出hello
    

    查看函数对象属性

    python函数是一种对象,是对象就会有对象的属性。可以通过如下方式查看函数对象的属性:

    dir(func_name)
    

    例如,有一个属性__name__,它表示函数的名称:

    def f(x):
        y=10
        def g(z):
            return x+y+z
        return g
    
    print(f.__name__)   # 输出f
    

    还有一个属性__code__,这个属性是本文的重点,它表示函数代码对象:

    print(f.__code__)
    

    输出:

    <code object f at 0x0335B180, file "a.py", line 2>
    

    上面的输出结果已经指明了__code__也是对象,既然是对象,它就有自己的属性:

    print( dir(f.__code__) )
    

    现在,就可以看到函数代码对象相关的属性,其中有一类属性都以co_开头,表示字节码的意思,后文会详细解释这些属性的意义。实际上,并非只有函数具有这些属性,所有的代码块(code block)都有这些属性。

    [...省略其它非co_属性...
    'co_argcount', 'co_cellvars',
    'co_code', 'co_consts',
    'co_filename', 'co_firstlineno',
    'co_flags', 'co_freevars',
    'co_kwonlyargcount', 'co_lnotab',
    'co_name', 'co_names', 'co_nlocals',
    'co_stacksize', 'co_varnames']
    

    如何查看这些__code__的属性?使用f.__code__.co_XXX即可。由于dir()返回的是属性列表,所以下面使用循环将co_开头的属性都输出出来:

    for i in dir(f.__code__):
        if i.startswith("co"):
            print(i+":",eval("f.__code__."+i))
    

    输出结果:

    co_argcount: 1
    co_cellvars: ('x', 'y')
    co_code: b'dx01x89x01x87x00x87x01fx02dx02dx03x84x08}x01|x01Sx00'
    co_consts: (None, 10, <code object g at 0x02FB7338, file "g:/pycode/b.py", line 3>, 'f.<locals>.g')
    co_filename: g:/pycode/b.py
    co_firstlineno: 1
    co_flags: 3
    co_freevars: ()
    co_kwonlyargcount: 0
    co_lnotab: b'x00x01x04x01x0ex02'
    co_name: f
    co_names: ()
    co_nlocals: 2
    co_stacksize: 3
    co_varnames: ('x', 'g')
    

    此外,还可以使用dis模块的show_code()函数来输出这些信息的整理:

    import dis
    def f(x):
        y=10
        def g(z):
            return x+y+z
        return g
    
    print(dis.show_code(f))
    

    输出结果:

    Name:              f
    Filename:          g:/pycode/b.py
    Argument count:    1
    Kw-only arguments: 0
    Number of locals:  2
    Stack size:        3
    Flags:             OPTIMIZED, NEWLOCALS
    Constants:
       0: None
       1: 10
       2: <code object g at 0x00A89338, file "g:/pycode/b.py", line 4>
       3: 'f.<locals>.g'
    Variable names:
       0: x
       1: g
    Cell variables:
       0: x
       1: y
    None
    

    __code__属性的解释

    这些属性定义在python源码包的Include/code.h文件中,如有需要,可自行去查看。

    另外,这些属性是代码块(code block)的,不限于函数。但此处以函数为例进行说明。

    由于这些属性中涉及到了闭包属性(或者嵌套函数的属性),所以以下面这个a.py文件中的嵌套函数为例:

    import dis
    x=3
    def f(a,b,*args,c):
        a=3
        y=10
        print(a,b,c,x,y)
        def g(z):
            return a+b+c+x+z
        return g
    

    以下是查看函数f()和闭包函数g()的方式:

    # f()的show_code结果
    dis.show_code(f)
    
    # f()的co_XXX属性
    for i in dir(f.__code__):
        if i.startswith("co"):
            print(i+":",eval("f.__code__."+i))
    
    # 闭包函数,注意,传递了*args参数
    f1=f(3,4,"arg1","arg2",c=5)
    
    # f1()的show_code结果
    dis.show_code(f1)
    
    # f1()的co_XXX属性
    for i in dir(f1.__code__):
        if i.startswith("co"):
            print(i+":",eval("f1.__code__."+i))
    

    下面将根据上面查看的结果解释各属性:

    co_name
    函数的名称。

    上例中该属性的值为外层函数f和闭包函数g,注意不是f1。

    co_filename
    函数定义在哪个文件名中。

    上例中为a.py

    co_firstlineno
    函数声明语句在文件中的第几行。即def关键字所在的行号。

    上例中f()的行号为3,g()的行号为7。

    co_consts
    该函数中使用的常量有哪些。python中并没有专门的常量概念,所有字面意义的数据都是常量。

    以下是show_code()得到的f()中的常量:

    Constants:
       0: None
       1: 3
       2: 10
       3: <code object g at 0x0326B7B0, file "a.py", line 7>
       4: 'f.<locals>.g'
    

    而内层函数g()中没有常量。

    co_kwonlyargcount
    keyword-only的参数个数。

    f()的keyword-only的参数只有c,所以个数为1
    g()中没有keyword-only类的参数,所以为0

    co_argcount
    除去*args之外的变量总数。实际上是除去***所收集的参数以及keyword-only类的参数之后剩余的参数个数。换句话说,是***前面的位置参数个数。

    f()中属于此类参数的有a和b,所以co_argcount数值为2
    g()中只有一个位置参数,所以co_argcount数值为1

    co_nlocals
    co_varnames
    本地变量个数和它们的名称,变量名称收集在元组中。

    f()的本地变量个数为6,元组的内容为:('a', 'b', 'c', 'args', 'y', 'g')
    g()的本地变量个数为1,元组的内容为:('z',)

    co_stacksize
    本段函数需要在栈空间评估的记录个数。换句话说,就是栈空间个数。

    这个怎么计算的,我也不知道。以下是本示例的结果:
    f()的栈空间个数为6
    g()的栈空间个数为2

    co_names
    函数中保存的名称符号,一般除了本地变量外,其它需要查找的变量(如其它文件中的函数名,全局变量等)都需要保存起来。

    f()的co_names:

    Names:
       0: print
       1: x
    

    g()的co_names:

    Names:
       0: x
    

    co_cellvars
    co_freevars
    这两个属性和嵌套函数(或者闭包有关),它们是互相对应的,所以内容完全相同,它们以元组形式存在。

    co_cellvars是外层函数的哪些本地变量被内层函数所引用
    co_freevars是内层函数引用了哪些外层函数的本地变量

    对外层函数来说,co_freevars一定是空元组,对内层函数来说,co_cellvars则一定是空元组。

    如果知道自由变量的概念,这个很容易理解。

    f()的co_cellvars内容: ('a', 'b', 'c')
    g()的co_freevars内容: ('a', 'b', 'c')

    co_code
    co_flags
    co_lnotab
    这3个属性和python函数的源代码编译成字节码有关,本文不解释它们。

    属性和字节码对象PyCodeObject

    对于python,通常都认为它是一种解释型语言。但实际上它在进行解释之前,会先进行编译,会将python源代码编译成python的字节码(bytecode),然后在python virtual machine(PVM)中运行这段字节码,就像Java一样。但是PVM相比JVM而言,要更"高级"一些,这个高级的意思和高级语言的意思一样:离物理机(处理机器码)的距离更远,或者说要更加抽象。

    源代码被python编译器编译的结果会保存在内存中一个名为PyCodeObject的对象中,当需要运行时,python解释器开始将其放进PVM中解释执行,执行完毕后解释器会"根据需要"将这个编译的结果对象持久化到二进制文件*.pyc中。下次如果再执行,将首先从文件中加载(如果存在的话)。

    所谓"根据需要"是指该py文件是否只运行一次,如果不是,则写入pyc文件。至少,对于那些模块文件,都会生成pyc二进制文件。另外,使用compileall模块,可以强制让py文件编译后生成pyc文件。

    但需要注意,pyc虽然是字节码文件,但并不意味着比py文件执行效率更高,它们是一样的,都是一行行地读取、解释、执行。pyc唯一比py快的地方在导入,因为它无需编译的过程,而是直接从文件中加载对象。

    py文件中的每一个代码块(code block)都有一个属于自己的PyCodeObject对象。每个代码块除了被编译得到的字节码数据,还包含这个代码块中的常量、变量、栈空间等内容,也就是前面解释的各种co_XXX属性信息。

    pyc文件包含3部分:

    • 4字节的Magic int,表示pyc的版本信息
    • 4字节的int,是pyc的产生时间,如果与py文件修改时间不同,则会重新生成
    • PycodeObject对象序列化的内容

    参考文章:https://blog.csdn.net/efeics/article/details/9255193

  • 相关阅读:
    基础知识梳理
    计算机基础
    IAR平台下使用STM32的DSP配置方法
    第五节:STM32输入捕获(用CubeMX学习STM32)
    第四节:定时器中断及定时器产生PWM(用CubeMX学习STM32)
    第三节: 串口通信(用CubeMX学习STM32)
    第二节: 外部中断学习(用CubeMX学习STM32)
    第一节补充: 按键操作(CubeMX加HAL库学STM32系列)
    第一节:用Cube学32之简单IO口操作(点灯及按键)
    STM32程序中使用printf打印中文字符乱码
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/9937018.html
Copyright © 2020-2023  润新知