Python之code对象

根据官方文档的解释Code Objects,翻译过来大意是:代码对象是CPython实现的底层细节。每一个都代表一段尚未绑定到函数中的可执行代码。

code对象概览

方法的__code__属性

>>> def foo():
...     print("我是foo")
...
>>> foo.__code__
<code object foo at 0x7f7dd16693a0, file "<stdin>", line 1>

使用compile方法

>>> code_str = """
... def foo():
...     print("我是foo")
... """
>>> compile(code_str, '<string>', 'exec')
<code object <module> at 0x7f7dd176b240, file "<string>", line 2>

运行code对象

code对象可以通过exec方法直接运行

>>> def foo():
...     print("我是方法foo")
...
>>>
>>> code_obj1 = foo.__code__
>>>
>>> code_str = """
... def foo():
...     print("我是模块foo")
... foo()
... """
>>>
>>> code_obj2 = compile(code_str, '<string>', 'exec')
>>>
>>> exec(code_obj1)
我是方法foo
>>> exec(code_obj2)
我是模块foo

替换__code__属性?

既然__code__对象可以单独运行,那么可以人为替换方法的code对象吗?

>>> def foo():
...     print("我是foo")
...
>>>
>>> def foo2():
...     print("我是foo2")
...
>>>
>>> foo2.__code__ = foo.__code__
>>>
>>> foo2()
我是foo

code对象属性详解

使用dir方法,并排除掉从object继承的属性:

>>> def foo():
...     print("我是方法foo")
...
>>>
>>> code_obj = foo.__code__
>>>
>>> set(dir(code_obj)) - set(dir(object))
{'co_filename', 'co_freevars', 'co_posonlyargcount', 'co_code', 'co_consts', 'replace', 'co_firstlineno', 'co_names', 'co_argcount', 'co_varnames', 'co_cellvars', 'co_name', 'co_nlocals', 'co_lnotab', 'co_kwonlyargcount', 'co_flags', 'co_stacksize'}

如下表格所示

AttributeDescription
co_argcountnumber of arguments (not including keyword only arguments, * or ** args)
co_codestring of raw compiled bytecode
co_cellvarstuple of names of cell variables (referenced by containing scopes)
co_conststuple of constants used in the bytecode
co_filenamename of file in which this code object was created
co_firstlinenonumber of first line in Python source code
co_flagsbitmap of CO_* flags, read more here
co_lnotabencoded mapping of line numbers to bytecode indices
co_freevarstuple of names of free variables (referenced via a function’s closure)
co_posonlyargcountnumber of positional only arguments
co_kwonlyargcountnumber of keyword only arguments (not including ** arg)
co_namename with which this code object was defined
co_namestuple of names of local variables
co_nlocalsnumber of local variables
co_stacksizevirtual machine stack space required
co_varnamestuple of names of arguments and local variables

co_code

代码的二进制bytecode,也就是字节码。

>>> def foo(a, b=1, *args, **kwargs):
...     print("a + b =", a + b)
...
>>>
>>> code_obj = foo.__code__
>>>
>>> print(code_obj.co_code)
b't\x00d\x01|\x00|\x01\x17\x00\x83\x02\x01\x00d\x00S\x00'

co_code代表的字节码看不懂,可以使用dis转换为人类可读的形式:

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('a + b =')
              4 LOAD_FAST                0 (a)
              6 LOAD_FAST                1 (b)
              8 BINARY_ADD
             10 CALL_FUNCTION            2
             12 POP_TOP
             14 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>>

co_name、co_filename、co_lnotab

  • co_name:当前代码被定义的名字,一般就是方法(模块)名
  • co_filename:创建这段代码的文件名
  • co_lnotab:是行号到字节码索引的编码映射,具体怎么压缩映射的可以看这里lnotab_notes.txt
def foo(a, b=1, *args, **kwargs):
    print("a + b =", a + b)


code_obj = foo.__code__

print(code_obj.co_name)
print(code_obj.co_filename)
print(code_obj.co_lnotab)

# 输出
# foo
# /Users/dengrunting/data/code/demos/code_obj/demo_05.py
# b'\x00\x01'

co_flags、co_stacksize

  • co_flags:是一个bit map,编译器编译的时候会根据co_flags的值,可能会有不同的处理方式,具体可以看这里Code Objects Bit Flags
  • co_stacksize:虚拟机需要的栈空间大小
def foo(a, b=1, *args, **kwargs):
    print("a + b =", a + b)


code_obj = foo.__code__

print(code_obj.co_flags)
print(code_obj.co_stacksize)

# 79
# 4

co_argcount、co_posonlyargcount、co_kwonlyargcount

  • co_argcount:参数个数,不包含只能位置传参的参数(示例代码中就是参数a,b, c,所以值是3)
  • co_posonlyargcount:只能通过位置传参的参数个数(示例代码中就是参数a,所以值是1)
  • co_kwonlyargcount:只能通过关键字传参的参数个数(示例代码中就是参数d,e,所以值是2)

提示:/之前的参数只能位置传参,*之后的参数只能关键字传参

def foo(a, /, b, c, *, d, e=10, **kwargs):
    print("a + b =", a + b)


code_obj = foo.__code__

print(code_obj.co_argcount)
print(code_obj.co_posonlyargcount)
print(code_obj.co_kwonlyargcount)

# 输出:
# 3
# 1
# 2

co_nlocals、co_varnames

  • co_varnames:局部变量个数
  • co_varnames:局部变量元祖
def foo(a, b, **kwargs):
    print("a + b =", a + b)
    x = 20


code_obj = foo.__code__

print("co_nlocals", code_obj.co_nlocals)
print("co_varnames", code_obj.co_varnames)

# 输出
# co_nlocals 4
# co_varnames ('a', 'b', 'kwargs', 'x')

co_freevars、co_cellvars、co_consts

  • co_freevars:闭包引用的变量名元祖(barco_freevarsab,是引用的foo中的,而fooco_freevars为空元祖)
  • co_cellvars:被所包含的作用域引用的变量名元祖(foo中的abbar引用了,sD引用了,所以结果为('a', 'b', 's')
  • co_consts:字节码中用到的常量
n = 30


def foo(a, b, **kwargs):
    s = a + b

    def bar():
        return a * b + n

    class D:
        z = s + 1

    print("co_freevars of bar", bar.__code__.co_freevars)
    return s


code_obj = foo.__code__

foo(1, 2)
print("co_freevars of foo", code_obj.co_freevars)

print("co_cellvars", code_obj.co_cellvars)
print("co_names", code_obj.co_names)
print("co_consts", code_obj.co_consts)

# 输出
# co_freevars of bar ('a', 'b')
# co_freevars of foo ()
# co_cellvars ('a', 'b', 's')
# co_names ('print', '__code__', 'co_freevars')
# co_consts (None, <code object bar at 0x7f86dfdd17c0, file "/Users/dengrunting/data/code/demos/code_obj/demo_05.py", line 4>, 'foo.<locals>.bar', <code object D at 0x7f86dfdd1870, file "/Users/dengrunting/data/code/demos/code_obj/demo_05.py", line 9>, 'D', 'co_freevars of bar')

co_names

解释不是很清楚,但是通过dis.dis(foo)可以看出,co_names的值跟LOAD_ATTRLOAD_GLOBAL有关(指令含义局可以看这里),所以基本可以理解为全局变量名和对象属性名的元祖。

def foo(a, b, **kwargs):
    class D:
        pass

    print("co_freevars of bar", D.x.y.z)
    return a + b


code_obj = foo.__code__

print("co_names", code_obj.co_names)

# 输出
# co_names ('print', 'x', 'y', 'z')

通过dis查看可读的字节码:

import dis

print(dis.dis(foo))

结果如下:

  2           0 LOAD_BUILD_CLASS
              2 LOAD_CONST               1 (<code object D at 0x7f8836d727c0, file "/Users/dengrunting/data/code/demos/code_obj/demo_06.py", line 2>)
              4 LOAD_CONST               2 ('D')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               2 ('D')
             10 CALL_FUNCTION            2
             12 STORE_FAST               3 (D)

  5          14 LOAD_GLOBAL              0 (print)
             16 LOAD_CONST               3 ('co_freevars of bar')
             18 LOAD_FAST                3 (D)
             20 LOAD_ATTR                1 (x)
             22 LOAD_ATTR                2 (y)
             24 LOAD_ATTR                3 (z)
             26 CALL_FUNCTION            2
             28 POP_TOP

  6          30 LOAD_FAST                0 (a)
             32 LOAD_FAST                1 (b)
             34 BINARY_ADD
             36 RETURN_VALUE

Disassembly of <code object D at 0x7f8836d727c0, file "/Users/dengrunting/data/code/demos/code_obj/demo_06.py", line 2>:
  2           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('foo.<locals>.D')
              6 STORE_NAME               2 (__qualname__)

  3           8 LOAD_CONST               1 (None)
             10 RETURN_VALUE
None
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值