Python 代码究竟是怎样“跑起来”的?从源码、AST、字节码到 CPython 专门化解释器

Python 代码究竟是怎样“跑起来”的?从源码、AST、字节码到 CPython 专门化解释器

很多人第一次学习 Python 时,会形成一个很自然的印象:

Python 是解释型语言,所以解释器大概是一行一行读取源码,然后一行一行执行。

这个理解不能说完全错,但如果你继续深入 Python 编程、性能优化、框架原理甚至 CPython 源码,就会发现真实过程要有意思得多。

看看这个再简单不过的函数:

def add(a, b):
    return a + b

当我们执行:

add(1, 2)

CPU 并不是直接理解 return a + b

在现代 CPython 中,它大致要经历这样一条链路:

Python 源代码
    ↓
Tokenize / Parse
    ↓
AST(抽象语法树)
    ↓
符号表分析
    ↓
Instruction Sequence
    ↓
CFG(控制流图)与编译期优化
    ↓
Bytecode
    ↓
Code Object
    ↓
Function Object
    ↓
Frame
    ↓
CPython Interpreter
    ↓
Python Object / C API
    ↓
CPU

CPython 官方内部文档给出的编译流程也基本如此:源码首先被词法分析、解析为 AST,然后生成指令序列,构造并优化控制流图,最终组装成 bytecode 和 PyCodeObject。(GitHub)

而更值得关注的是:从 Python 3.11 开始,CPython 执行的字节码甚至可能在运行过程中根据真实数据类型被“专门化”。

这也是为什么同一段 Python 代码,在运行一段时间以后,有时会比刚启动时更快。(Python Enhancement Proposals (PEPs))

本文就沿着 add() 这个函数,一层一层把 Python 的执行过程拆开。

**版本说明:**本文讨论的是 CPython,重点面向 Python 3.11 之后的实现。下面部分 dis 输出使用 CPython 3.13.x 作为示例。Python 官方明确说明 bytecode 属于 CPython 实现细节,不保证不同 Python 版本之间保持不变,因此你在 3.12、3.13、3.14 中看到的操作码名称可能不同。(Python documentation)


一、第一站:源码并不会直接交给 CPU

我们的源码是:

def add(a, b):
    return a + b

对人来说,它已经足够清楚:

  1. 定义一个函数;
  2. 接受两个参数;
  3. 对二者执行加法;
  4. 返回结果。

但是解释器首先需要回答一些更基础的问题:

  • def 是什么?
  • add 是函数名还是变量?
  • ab 属于什么作用域?
  • + 是哪一种表达式?
  • return 的返回值是什么?
  • 整个程序的语法是否合法?

所以源码首先要被解析。

我们可以自己看看 Python 眼中的这段代码。

import ast

source = """
def add(a, b):
    return a + b
"""

tree = ast.parse(source)

print(
    ast.dump(
        tree,
        indent=4
    )
)

你会看到类似:

Module(
    body=[
        FunctionDef(
            name='add',
            args=arguments(
                args=[
                    arg(arg='a'),
                    arg(arg='b')
                ]
            ),
            body=[
                Return(
                    value=BinOp(
                        left=Name(id='a', ctx=Load()),
                        op=Add(),
                        right=Name(id='b', ctx=Load())
                    )
                )
            ]
        )
    ]
)

这就是 AST:Abstract Syntax Tree,抽象语法树

它已经不太关心源码究竟写成:

return a+b

还是:

return    a     +     b

而更关心程序的结构:

FunctionDef: add
│
├── 参数 a
├── 参数 b
│
└── Return
     │
     └── BinOp (+)
          ├── Name(a)
          └── Name(b)

换句话说:

源码描述“程序长什么样”,AST 描述“程序是什么意思”。

CPython 当前使用 PEG parser,将 token 流解析为 AST;AST 随后进入编译器的后续阶段。(GitHub)

这也是为什么 ast 模块在静态分析、代码检查、自动重构、代码生成等工具中如此重要。

例如,你完全可以找出某段代码里所有函数:

for node in ast.walk(tree):
    if isinstance(node, ast.FunctionDef):
        print(node.name)

输出:

add

很多 lint、IDE、代码扫描器的思想,本质上都和这种结构化分析有关。


二、AST 之后发生什么?进入真正的“编译阶段”

这里有一个非常重要的认知:

CPython 并不是“不编译”。

它只是不是通常意义上直接把 Python 源码编译成 x86、ARM 机器码。

通常情况下,CPython 会把源码编译成自己的 bytecode(字节码)

根据 CPython 编译器内部文档,大致经历:

Source
 ↓
AST
 ↓
Symbol Table
 ↓
Instruction Sequence
 ↓
CFG
 ↓
Optimization
 ↓
Bytecode
 ↓
Code Object

其中还有一个经常被 Python 开发者忽视的重要阶段:符号表分析

例如:

x = 10

def foo():
    y = 20
    return x + y

编译器必须判断:

x → global
y → local

再复杂一点:

def outer():
    x = 10

    def inner():
        return x

    return inner

这里 x 又会涉及 closure / free variable。

这些信息不能等真正执行时才完全从零开始判断,编译阶段就会提前完成大量作用域分析。


三、Code Object:真正值得认识的 Python 内部对象

我们现在直接拿到 add 的 code object:

def add(a, b):
    return a + b

code = add.__code__

print(code)

可能得到:

<code object add at 0x..., file "...", line 1>

可以进一步观察:

print(code.co_name)
print(code.co_argcount)
print(code.co_varnames)
print(code.co_consts)
print(code.co_stacksize)

例如:

add
2
('a', 'b')
(None,)
2

Code Object 可以理解为:

已经编译完成、等待执行的一份 Python 程序描述。

其中包含的不只是 bytecode,还包括:

co_code       字节码相关信息
co_consts     常量
co_names      使用到的名称
co_varnames   局部变量
co_argcount   参数数量
co_stacksize  所需执行栈大小
co_filename   来源文件
...

Python 官方数据模型同样将 code object 定义为表示已经 byte-compile 的可执行 Python 代码;函数对象和 code object 并不是同一个东西。函数还拥有 globals、默认参数等运行时上下文。(Python documentation)

这一点非常重要。


四、一个容易被忽略的细节:def 本身也是要执行的

很多人以为:

def add(a, b):
    return a + b

运行模块时,解释器直接“得到”了函数。

其实还不是。

我们自己编译一下:

source = """
def add(a, b):
    return a + b
"""

module_code = compile(
    source,
    "demo.py",
    "exec"
)

print(module_code.co_consts)

你可能看到:

(
    <code object add at ...>,
    None
)

有趣的地方来了。

add 的 code object 被放进了模块 code object 的常量区域。

继续:

import dis

dis.dis(module_code)

在某些现代 CPython 版本中,可以看到类似:

LOAD_CONST       <code object add ...>
MAKE_FUNCTION
STORE_NAME       add

也就是说,执行:

def add(...):

实际上可以粗略理解为:

取得 add 的 Code Object
        ↓
创建 Function Object
        ↓
把函数对象绑定给名字 add

所以:

add

是一个 function object

而:

add.__code__

才是它背后的 code object

这个区别在理解:

  • 装饰器;
  • 闭包;
  • 动态函数;
  • monkey patch;
  • descriptor;
  • 函数热替换;

时非常关键。


五、dis.dis(add) 到底能看到什么?

终于来到很多 Python 进阶教程中最神秘的工具:

import dis

dis.dis(add)

以某些 CPython 3.13 构建为例,可能出现:

RESUME
LOAD_FAST_LOAD_FAST    (a, b)
BINARY_OP              (+)
RETURN_VALUE

不同版本输出会不同,所以不要死记操作码。

Python 官方专门提醒:bytecode 是 CPython implementation detail,操作码可以随版本增加、删除或者修改。(Python documentation)

真正应该学习的是怎么读


1. LOAD_FAST

假设版本中显示:

LOAD_FAST 0 (a)
LOAD_FAST 1 (b)

含义可以粗略理解为:

把局部变量 a 放到执行栈
把局部变量 b 放到执行栈

为什么叫 FAST

因为局部变量在函数执行时拥有专门的高效存储结构,并不是每次都简单等价于:

locals()["a"]

进行字典查询。


2. BINARY_OP

接下来:

BINARY_OP 0 (+)

意味着从栈顶取出两个对象:

      ┌─────┐
TOP → │  b  │
      ├─────┤
      │  a  │
      └─────┘

执行:

a + b

再把结果压回去:

      ┌───────┐
TOP → │ a + b │
      └───────┘

官方 dis 文档对 BINARY_OP 的抽象描述基本就是:

rhs = STACK.pop()
lhs = STACK.pop()
STACK.append(lhs op rhs)

(Python documentation)


3. RETURN_VALUE

最后:

RETURN_VALUE

把当前栈顶结果作为函数返回值。

所以:

def add(a, b):
    return a + b

从虚拟机角度看,可以非常粗略地理解成:

LOAD a
LOAD b
ADD
RETURN

这已经非常接近虚拟机真正关心的世界了。


六、Bytecode 和 Code Object 到底是什么关系?

可以用一个简单比喻。

假设 Code Object 是一本“施工手册”:

Code Object
├── bytecode:施工步骤
├── constants:材料清单
├── variable names:变量目录
├── source positions:源码位置
├── stack size:需要多大的工作台
└── exception info:异常处理路径

那么 bytecode 只是其中最核心的一部分,并不等于整个 Code Object。

实际上 Python 官方 C API 文档甚至特别提醒:现代 CPython 中,通过某些接口取得的 co_code 表示并不一定就是解释器最终实际执行的内部字节码形态,因为运行时还可能进行专门化。(Python documentation)

这就引出了本文最精彩的一部分。


七、CPython Interpreter 怎样执行 Bytecode?

调用:

add(1, 2)

时,解释器还需要一个东西:

Frame

Code Object 更像“静态程序”。

Frame 则保存“一次具体执行”的动态状态。

可以粗略理解:

Code Object
    +
实际参数
    +
globals
    +
builtins
    +
指令位置
    +
执行栈
    +
异常状态
    ↓
Frame

如果:

add(1, 2)
add(100, 200)

它们执行的是同一个 add.__code__

但属于两次不同的函数调用,因此会拥有不同的执行状态。

CPython 内部解释器文档描述得很明确:执行一个 CodeObject 时会构造 frame,frame 保存指令指针、globals、builtins 等动态执行状态,随后交给 evaluation loop 执行。(GitHub)

经典理解中,解释器核心逻辑可以想象成:

for (;;) {
    opcode = NEXT_OPCODE();

    switch (opcode) {
        case LOAD_FAST:
            ...
            break;

        case BINARY_OP:
            ...
            break;

        case RETURN_VALUE:
            ...
            break;
    }
}

当然,真实 CPython 要复杂得多。

但这个模型非常有用:

CPython interpreter 本质上是一台执行 Python bytecode 的虚拟机。


八、问题来了:a + b 到底慢在哪里?

假设我们第一次执行:

add(1, 2)

人类已经知道:

a 是 int
b 是 int

但函数定义本身并不知道。

下一次可能是:

add("Hello ", "Python")

甚至:

class Money:
    def __add__(self, other):
        ...

add(Money(), Money())

所以一个通用的:

BINARY_OP +

不能简单等同于 CPU 的整数加法。

它需要考虑:

int + int?
float + float?
str + str?
用户自定义 __add__?
右操作数 __radd__?
子类?
失败后抛 TypeError?

动态语言的灵活性,是需要运行时成本的。

那么问题来了:

如果一个 BINARY_OP 连续执行了成千上万次,而且每一次都是 int + int,为什么还要永远走最通用的路径?

这正是 specializing adaptive interpreter 要解决的问题。


九、Adaptive / Specializing Interpreter 到底优化什么?

从 Python 3.11 开始,CPython 引入了 specializing adaptive interpreter。

它的核心思想其实非常朴素:

先观察,再下注;下注正确就走快路,环境变化就退回来。

PEP 659 将这个过程称为 specialization 和 quickening:一个适合优化的指令运行足够多次以后,可以被替换成针对实际类型或值模式优化的版本;如果之后假设持续失败,还可以反向 de-optimize。(Python Enhancement Proposals (PEPs))

例如:

def add(a, b):
    return a + b

刚开始可能是:

BINARY_OP

假设不断执行:

for _ in range(100_000):
    add(1, 2)

解释器逐渐观察到:

这里几乎一直都是:

int + int
int + int
int + int
int + int
...

那么通用指令就可能专门化成类似:

BINARY_OP_ADD_INT

在一个 CPython 3.13.x 环境中,预热后实际就可能观察到:

BINARY_OP_ADD_INT

这背后的意思不是:

“Python 突然变成 C 了。”

而是:

“解释器已经知道这个热点位置大概率处理两个整数,因此可以绕开一部分通用动态分派工作。”

PEP 659 明确指出,这种 specialization 即使没有把代码生成成本地机器码,同样可以带来性能收益。(Python Enhancement Proposals (PEPs))

因此它和传统意义上的 JIT 不是同一个概念。


十、亲手观察:“预热前”和“预热后”有什么不同?

这是本文最推荐你亲自运行的实验。

import dis

def add(a, b):
    return a + b

print("=== before ===")
dis.dis(
    add,
    adaptive=True,
    show_caches=True
)

for _ in range(100_000):
    add(1, 2)

print("=== after ===")
dis.dis(
    add,
    adaptive=True,
    show_caches=True
)

在支持 specialization 的 CPython 中,你可能观察到类似变化:

预热前

BINARY_OP
CACHE

预热后

BINARY_OP_ADD_INT
CACHE

注意:

dis.dis(add)

和:

dis.dis(
    add,
    adaptive=True,
    show_caches=True
)

观察到的东西并不完全一样。

官方 dis 文档说明:

  • show_caches=True 可以显示 specialization 使用的 inline cache;
  • adaptive=True 可以显示运行时已经专门化的 bytecode,而不是只看原始形式。(Python documentation)

在 Python 3.14 的 python -m dis 命令行工具中,还增加了 -S / --specialized 来显示 specialized bytecode。(Python documentation)


十一、属性访问的优化甚至更有意思

看一个更接近真实业务代码的例子:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


def total(p):
    return p.x + p.y

先看:

p = Point(10, 20)

dis.dis(
    total,
    adaptive=True,
    show_caches=True
)

然后预热:

for _ in range(100_000):
    total(p)

再次:

dis.dis(
    total,
    adaptive=True,
    show_caches=True
)

在合适的 CPython 版本中,你可能看到类似:

LOAD_ATTR

逐渐变成:

LOAD_ATTR_INSTANCE_VALUE

以及:

BINARY_OP

变成:

BINARY_OP_ADD_INT

为什么 LOAD_ATTR 很适合优化?

因为一般意义上的:

obj.x

背后可能涉及:

实例属性?
类属性?
descriptor?
property?
__getattribute__?
__getattr__?
__slots__?
module attribute?

这是一个非常动态的操作。

但是如果运行时不断证明:

这里一直是普通实例
x 一直位于稳定的位置
类结构没有变化

解释器就可以缓存一些信息,让下一次属性访问走更短的路径。

PEP 659 甚至专门以 LOAD_ATTRLOAD_GLOBAL 为案例说明了这种 specialization:针对实例属性、模块属性、__slots__ 等不同情况使用更加专门的执行路径,并配合 inline cache 保存辅助信息。(Python Enhancement Proposals (PEPs))

这就是现代 CPython 性能优化越来越重要的方向之一:

不是取消 Python 的动态能力,而是让“稳定的动态行为”更便宜。


十二、Inline Cache 又是什么?

你使用:

dis.dis(
    total,
    adaptive=True,
    show_caches=True
)

可能看到不少:

CACHE
CACHE
CACHE

不要把它们简单理解成普通 Python 指令。

它们主要用于保存 specialization 所需要的运行时信息。

可以粗略想象:

LOAD_ATTR x
│
├── 这个类型是否还是原来的类型?
├── 字典结构有没有变化?
├── 属性大致在哪里?
└── specialization 是否持续成功?

下一次再执行时,如果这些假设仍然成立,就走快路。

如果不成立:

假设失败
   ↓
fallback
   ↓
通用执行路径
   ↓
必要时重新 specialization

所以它为什么叫 adaptive

因为它不是:

优化一次 → 永远不变

而更接近:

观察
 ↓
专门化
 ↓
继续验证
 ├── 成功 → 保持优化
 └── 失败 → 降级 / 重新适应

PEP 659 描述的 specialized instruction 会根据成功和失败调整计数,在假设长期失效时重新退回 adaptive 状态。(Python Enhancement Proposals (PEPs))


十三、终于回答:为什么同一段 Python 代码运行一段时间后可能变快?

现在可以给出一个完整答案。

原因一:解释器完成了 specialization

刚执行时:

BINARY_OP
LOAD_ATTR
LOAD_GLOBAL
CALL

可能还需要走较通用的逻辑。

热点代码执行一段时间以后,解释器已经掌握真实运行模式,例如:

这里一直是 int + int
这里一直访问同一种实例
这个 global 查找结构一直稳定
这里一直调用相似目标

于是部分操作可以进入 specialized fast path。(Python Enhancement Proposals (PEPs))


原因二:Inline Cache 已经“热起来”

第一次执行时,缓存还没有足够的信息。

后来:

类型信息
属性结构
命名空间状态
热点执行情况

等信息逐渐稳定,重复操作就可能减少一些重复工作。


原因三:还有 CPU 和系统层面的“热身”

不要把所有 warm-up 都归功于 Python specialization。

程序执行几轮后,性能改善还可能来自:

  • CPU instruction cache;
  • CPU data cache;
  • branch predictor;
  • 操作系统文件缓存;
  • 动态链接库初始化;
  • 第三方库自己的缓存;
  • 内存分配器状态;
  • 网络连接池;
  • 数据库连接池。

因此:

“第二次比第一次快”不等于“CPython 一定进行了 specialization”。

要判断 specialization,最直接的方法仍然是观察:

dis.dis(
    func,
    adaptive=True,
    show_caches=True
)

十四、这对性能测试有什么实际影响?

影响非常大。

下面这种测试:

import time

start = time.perf_counter()
result = add(1, 2)
end = time.perf_counter()

print(end - start)

几乎没有什么性能分析价值。

你测到的可能包括:

函数首次执行
+
解释器状态
+
计时器本身开销
+
CPU 调度
+
缓存冷启动

更合理的微基准测试方式是使用:

import timeit

def add(a, b):
    return a + b

for _ in range(50_000):
    add(1, 2)

result = timeit.repeat(
    "add(1, 2)",
    globals=globals(),
    number=1_000_000,
    repeat=5
)

print(result)
print("best:", min(result))

这里有三个实践原则。

第一,不要只执行一次。

单次测试噪声极大。

第二,要区分 cold performance 和 warm performance。

服务启动性能与长期运行性能并不是一个问题。

第三,不要为了“帮助 specialization”写奇怪代码。

优化顺序仍然应该是:

先选择正确算法
↓
减少无意义工作
↓
减少 I/O
↓
改善数据结构
↓
profile 找热点
↓
最后研究解释器级优化

O(n²) 改成 O(n log n),通常比研究某个 bytecode 快几个百分点重要得多。


十五、.pyc 又位于哪一层?

当我们导入 Python 模块时,经常能看到:

__pycache__/

里面有:

xxx.cpython-314.pyc

一个非常常见的误解是:

.pyc 是不是类似 C 编译出来的机器码?

不是。

它与 CPython bytecode / code object 层更接近,而不是 CPU 可以直接执行的原生机器指令。

当模块再次 import 时,如果缓存仍然有效,Python 可以利用缓存结果,从而避免重新完成全部源码编译流程。Python import 系统在加载 .pyc 前还会检查缓存是否与源码匹配。(Python documentation)

因此可以粗略理解:

第一次:

.py
 ↓
parse
 ↓
compile
 ↓
code object / bytecode
 ↓
可能生成 .pyc


后续 import:

.pyc
 ↓
恢复已编译结果
 ↓
执行

但注意:

.pyc 主要减少的是编译相关工作,并不会让你函数中的每个 a + b 自动变成本地机器指令。


十六、一套非常实用的 Python“拆解程序”工具箱

以后遇到一个陌生函数,可以按下面顺序研究。

假设:

def calculate(price, count):
    total = price * count
    return total * 0.9

第一步:看 AST

import ast
import inspect

source = inspect.getsource(calculate)

tree = ast.parse(source)

print(ast.dump(tree, indent=4))

解决:

“编译器怎么看这段源码?”


第二步:看 Code Object

code = calculate.__code__

print(code.co_varnames)
print(code.co_consts)
print(code.co_names)
print(code.co_stacksize)

解决:

“这段函数编译后保存了哪些静态信息?”


第三步:看普通 bytecode

import dis

dis.dis(calculate)

解决:

“源码最终被拆成了哪些虚拟机操作?”


第四步:看 inline cache

dis.dis(
    calculate,
    show_caches=True
)

解决:

“哪些位置拥有运行时缓存能力?”


第五步:预热以后观察 specialization

for _ in range(100_000):
    calculate(99.0, 5)

dis.dis(
    calculate,
    adaptive=True,
    show_caches=True
)

解决:

“解释器根据真实运行模式做了什么优化?”

这五步,是非常值得加入 Python 高级开发者工具箱的一套方法。


十七、理解执行模型,对日常 Python 开发到底有什么用?

有人可能会问:

“我又不开发 CPython,为什么要知道这些?”

因为理解底层执行模型,会直接改变你理解代码的方式。

当你知道:

obj.name

不是免费的,就更容易理解为什么大量动态属性访问可能成为热点。

当你知道:

global_name

需要名称解析,就更容易理解 CPython 为什么要尝试 specialization LOAD_GLOBAL

当你知道:

a + b

本质上是对象协议,而不是简单 CPU ADD,就更容易理解 Python 动态类型为什么强大、又为什么需要运行时成本。

当你理解:

source
→ AST
→ bytecode
→ frame
→ interpreter

很多过去看起来“玄学”的概念都会突然连起来:

  • decorator;
  • closure;
  • traceback;
  • profiler;
  • debugger;
  • coroutine;
  • generator;
  • exec()
  • compile()
  • inspect
  • ast
  • dis
  • 性能 profiling。

它们并不是彼此孤立的高级技巧,而是在同一套执行系统的不同位置观察 Python。


十八、最后建立一张完整的心智地图

回到最初:

def add(a, b):
    return a + b

现在我们已经可以看到它背后的完整故事。

① Source Code

def add(a, b):
    return a + b

        ↓

② Token / Parser

理解 def、参数、return、+

        ↓

③ AST

FunctionDef
 └── Return
      └── BinOp(+)

        ↓

④ Symbol Table

a → local
b → local

        ↓

⑤ Compiler / CFG

生成并优化指令序列

        ↓

⑥ Bytecode + Metadata

LOAD...
BINARY_OP
RETURN_VALUE

        ↓

⑦ Code Object

bytecode
constants
variables
source positions
stack information
...

        ↓

⑧ Function Object

add
 ├── __code__
 ├── __globals__
 ├── __defaults__
 └── ...

        ↓

⑨ Call

add(1, 2)

        ↓

⑩ Frame

locals
stack
instruction pointer
globals
builtins
...

        ↓

⑪ CPython Interpreter

读取 opcode
执行 opcode
维护 Python object

        ↓

⑫ Adaptive Specialization

观察热点代码与真实类型

BINARY_OP
        ↓
BINARY_OP_ADD_INT

        ↓

⑬ CPU 最终执行 CPython 的底层机器指令

这才是“Python 代码跑起来”更接近真实世界的答案。


写在最后

Python 最迷人的地方之一,就是它可以同时拥有两个完全不同的世界。

在表面上:

def add(a, b):
    return a + b

简单得几乎像一句自然语言。

而往下一层:

AST
Code Object
Bytecode
Frame
Eval Loop
Inline Cache
Quickening
Specialization

又足够一个开发者研究很多年。

也正因为如此,学习 Python 编程不能永远停留在“语法会不会写”。

当你开始追问:

dis.dis() 为什么是这些指令?

__code__ 到底装了什么?

obj.x 为什么能够被 specialization?

为什么一个函数刚启动和运行一段时间后的 bytecode 可能不一样?

你已经从“会使用 Python”,走向了“理解 Python”。

而这种理解最终会反馈到真正的 Python 实战中:你会更准确地分析性能、更从容地阅读框架源码,也更容易判断一个所谓的“Python 最佳实践”究竟是可靠经验,还是未经验证的性能玄学。

最后留两个问题:

你有没有遇到过某段 Python 代码第一次执行明显更慢、之后逐渐稳定的情况?最终找到的原因是什么?

如果解释器能够越来越聪明地根据运行时信息优化代码,那么未来 Python 的“解释执行”和“编译执行”之间,还会有多清晰的边界?

欢迎把你的实验结果、dis 输出和性能案例放在一起比较。真正理解 Python,往往就是从这些小小的“为什么”开始。🚀


参考与延伸阅读

本文涉及的 CPython 编译与执行细节主要可继续阅读:

  • Python dis 官方文档:介绍 bytecode 反汇编、inline cache、adaptive bytecode,并特别说明字节码属于 CPython 实现细节。(Python documentation)
  • CPython Compiler Design:源码到 AST、instruction sequence、CFG、bytecode 和 code object 的完整编译过程。(GitHub)
  • Python Data Model — Code Objects:了解 co_constsco_varnamesco_stacksize 等结构。(Python documentation)
  • PEP 659 — Specializing Adaptive Interpreter:深入理解 quickening、inline cache、specialization 与 de-optimization。(Python Enhancement Proposals (PEPs))
  • CPython Bytecode Interpreter Internal Docs:继续深入 frame、evaluation loop 与 interpreter 内部结构。(GitHub)

**SEO 关键词:**Python编程、Python教程、Python实战、Python最佳实践、Python字节码、Python AST、CPython、dis模块、Code Object、Python性能优化、Specializing Interpreter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭渊老黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值