在 Python 中,函数是“一等对象”,它们不仅仅包含可调用的逻辑,还携带大量由解释器维护的内部状态。其中最核心的结构之一,就是函数的 __code__ 属性。

__code__ 是一个code object(代码对象),包含了执行所需的所有静态信息:字节码、局部变量表、常量表、闭包变量、行号表、运行栈需求等。

理解 code object 是深入掌握 Python 执行模型的重要一步。

一、__code__ 是什么?

当 Python 执行一个 def 时,发生三件事:

• 源代码被编译成 bytecode(字节码)

• 字节码被封装为一个 code object(代码对象)

• 代码对象再被包在一个 function object(函数对象)中

结构示意:

源代码 → code object → function object

因此:

• __code__ 是函数的“可执行内核”

• 函数本身只是对 code object 的一层包装(包含名字、默认值、注解、闭包等)

示例:

print(type(f.__code__))

输出:

重点特性:

(1)code object 是不可变的(immutable)。

(2)但可以利用 types.CodeType() 创建新的 code 对象来替换函数的 __code__。

(3)Python 3.11+ 中 code object 内部结构大幅修改(但属性接口基本保持兼容)。

二、__code__中包含哪些信息

__code__ 记录了函数执行所需的全部静态信息,主要分为四类。

(1)参数与变量信息

属性名

含义

co_argcount

位置参数数量

co_posonlyargcount

仅限位置参数数量(3.8+)

co_kwonlyargcount

仅关键字参数数量

co_varnames

所有局部变量名(包含参数)

co_nlocals

局部变量数量

示例:

print(test.__code__.co_nlocals)   # 3

提示:

若需要所有局部变量名,不要只看 co_varnames,而应合并 co_varnames + co_cellvars。这是实践中常见的做法。

(2)名称解析相关

属性名

含义

co_names

函数体内引用的全局变量、属性名等

co_consts

常量表(字面量、内部函数 code 对象等)

co_qualname

函数的完整名称(含类名等)

示例:

print(f.__code__.co_consts)  # (None, 1)

注意:

co_consts[0] 通常是 None(函数隐式返回 None)。

(3)闭包结构

属性名

含义

co_freevars

当前函数中使用,但定义在外层作用域的变量

co_cellvars

当前函数中定义,并被内部函数引用的变量

示例:

print("inner.co_cellvars:", fn.__code__.co_cellvars)

输出:

inner.co_cellvars: ()

解释:

名称

在 outer 中

在 inner 中

归类

a

参数,被 inner 使用 → cellvar

来自外层 → freevar

cellvar + freevar

b

局部变量,被 inner 使用 → cellvar

来自外层 → freevar

cellvar + freevar

c

局部变量,被 inner 使用 → cellvar

来自外层 → freevar

cellvar + freevar

inner

outer 的局部变量,但未被捕获

只在 outer.co_varnames

简要说明:

• cellvars:我定义,内部函数你使用

• freevars:我使用,外层函数定义

(4)字节码与执行元信息

属性名

含义

co_code

字节码(3.11 前单纯的 bytecode,3.11+ 结构变化但属性仍存在)

co_firstlineno

函数起始行号

co_stacksize

执行时需要的栈大小

co_flags

标志位(是否为生成器、是否包含 varargs 等)

co_linetable/ co_exceptiontable

(3.11+)行号表与异常表

(5)Python 3.11+ 的新结构

从 3.11 开始,字节码结构发生重大变化:

新增 co_exceptiontable(替代旧行号表处理异常)

新增 co_linetable(替代 co_lnotab)

字节码采用 adaptive bytecode(动态优化的自适应字节码)

三、__code__与字节码

一个函数最终执行的是字节码(bytecode),可以使用 dis 模块查看。

示例:

dis.dis(f)

输出(示例):

              6 RETURN_VALUE

Python 3.11 的字节码更加复杂(adaptive),但 code object 属性仍提供统一访问入口。

四、__code__的生命周期

(1)函数定义时创建(def 执行就生成 code 对象)。

(2)__code__ 是不可变的(保证执行安全)。

(3)多个函数可能共享同一个 __code__。

例如:

print({id(fn.__code__) for fn in lst})

这些 lambda 会共享同一 code 对象。

(4)重新定义同名函数会创建新的 code 对象。

(5)无引用时由 GC 自动回收。

五、修改__code__

由于 __code__ 可被赋值,我们可以动态改变函数行为,这属于 Python 的元编程能力:

print(f())   # 999

注意事项:

• 参数数量必须一致,否则抛 ValueError

• freevars/cellvars 数量必须一致

• 适用于调试器、框架、元编程,不建议在业务代码中使用

小结

__code__ 是 Python 函数的可执行核心,记录了字节码、局部变量、常量表、闭包结构、行号表等全部静态信息。

函数只是对 code object 的包装,而 code object 在函数定义时由编译器生成,并在运行期间保持不可变。通过 co_freevars 和 co_cellvars 可以理解闭包如何捕获外层变量,通过 co_code 和 dis 可以了解 Python 的执行模型,而在必要时替换 __code__ 还能动态改变函数行为。

打开网易新闻 查看精彩图片

点赞有美意,赞赏是鼓励