python源码是如何运行起来的

为什么要了解底层原理

  • 写出高质量代码

  • 问题定位

  • 满足好奇心

  • 机械通感

开始

当我们编写并运行一行 print('Hello, World!') 时,背后究竟发生了什么?Python 代码是如何从我们可读的文本,变成计算机可以执行的指令的呢?

很多人将 Python 称为一门“解释型语言”,但这其实不完全准确。更精确地说,Python 是一门先编译后解释的语言,这点与java其实非常相似。

它的运行过程涉及两个核心阶段:

  1. 编译阶段:Python 解释器首先将我们编写的源代码(.py 文件)编译成一种称为字节码 (Bytecode) 的中间形式。

  2. 解释阶段:编译产生的字节码随后由 Python 虚拟机 (Python Virtual Machine, PVM) 来解释执行。

Part.1 从 Python 源代码到字节码

Python 代码的生命周期始于我们编写的 .py 文件。当我们执行一个 Python 脚本时,解释器并不直接逐行解析这些文本。相反,它会先进行一个“预处理”步骤——编译成字节码。

什么是字节码?

字节码是一种低级、与平台无关的指令集。它不像机器码那样是给物理 CPU 执行的,而是专门为 Python 虚拟机 (PVM) 设计的。这个设计使得 Python 代码拥有了出色的可移植性——同一份 Python 代码可以在任何安装了 Python 解释器的操作系统上运行,因为字节码是标准化的。

这个过程主要分为两个核心步骤:词法分析 (Lexical Analysis) 和 语法分析 (Parsing)。可以将它类比为我们阅读句子的过程:首先识别出每个单词(词法分析),然后理解整个句子的语法结构(语法分析)。

核心流程:

源代码 -> [词法分析器] -> 令牌流 -> [语法分析器] -> 抽象语法树 (AST)->字节码

第 1 步:词法分析 (Lexical Analysis) - 将代码分解为“token”

当 Python 解释器拿到您的源代码(一串纯文本字符)时,它做的第一件事不是去理解代码的逻辑,而是先将其打碎成一个个有意义的最小单元。这些单元被称为 “令牌” (Token)。

一个令牌通常包含两部分信息:

类型:例如 NAME (名称/标识符), NUMBER (数字), OP (操作符), KEYWORD (关键字)。

值:这个令牌对应的具体文本,例如 result, 10, +。

举个例子,对于我们之前用的代码 result = a + b,词法分析器会扫描这段文本,并生成类似下面这样的一个“令牌流”:

NAME: 'result'
OP: '='
NAME: 'a'
OP: '+'
NAME: 'b'

您可以把这个阶段看作是给代码“断词”。Python 内置的 tokenize 模块可以让我们亲眼看到这个过程:

import tokenize
from io import BytesIOcode = """
result=a+b
"""tokens = tokenize.tokenize(BytesIO(code.encode('utf-8')).readline)for token in tokens:print(token)

结果

TokenInfo(type=63 (ENCODING), string='utf-8', start=(0, 0), end=(0, 0), line='')
TokenInfo(type=62 (NL), string='\n', start=(1, 0), end=(1, 1), line='\n')
TokenInfo(type=1 (NAME), string='result', start=(2, 0), end=(2, 6), line='result=a+b\n')
TokenInfo(type=54 (OP), string='=', start=(2, 6), end=(2, 7), line='result=a+b\n')
TokenInfo(type=1 (NAME), string='a', start=(2, 7), end=(2, 8), line='result=a+b\n')
TokenInfo(type=54 (OP), string='+', start=(2, 8), end=(2, 9), line='result=a+b\n')
TokenInfo(type=1 (NAME), string='b', start=(2, 9), end=(2, 10), line='result=a+b\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(2, 10), end=(2, 11), line='result=a+b\n')
TokenInfo(type=0 (ENDMARKER), string='', start=(3, 0), end=(3, 0), line='')

类型名

描述

NAME

变量名、关键字等

NUMBER

数值

STRING

字符串

OP

运算符

INDENT

缩进

DEDENT

取消缩进

NEWLINE

语句结束

ENCODING

文件编码(如 utf-8)

COMMENT

注释

NL

非逻辑新行

ENDMARKER

文件结束标记

第2步:语法分析

现在我们有了一个扁平的、线性的令牌列表,但解释器还不知道它们之间的关系。这时 解析器 (Parser) 就登场了。

解析器的任务是接收词法分析器生成的令牌流,并根据 Python 语言预设的语法规则 (Grammar),将这些令牌组织成一个具有层级结构的树。这个树就是抽象语法树 (AST)。

继续我们的例子 result = a + b:

解析器拿到 NAME, OP('='), NAME, OP('+'), NAME 这个令牌序列。

它根据 Python 的语法规则,识别出这是一个“赋值语句”的模式。

于是它创建一个 Assign (赋值) 节点作为根。

语法规定,= 左边的是赋值目标 (target),于是它把第一个 NAME('result') 令牌作为 Assign 节点的 targets 子节点。

= 右边的 a + b 是要赋的值 (value)。解析器接着分析 NAME('a'), OP('+'), NAME('b') 这部分。

它识别出这是一个“二元运算” (Binary Operation) 的模式。

于是它创建一个 BinOp 节点,并将其作为 Assign 节点的 value 子节点。

语法规定,+ 是操作符 (op),a 是左操作数 (left),b 是右操作数 (right)。解析器相应地创建 Add、Name('a') 和 Name('b') 节点,并将它们作为 BinOp 节点的子节点。

经过这一系列操作,一个扁平的令牌流就变成了一个能清晰反映代码逻辑和结构的树状数据结构——AST。这个 AST 完整地表达了“将变量 a 和 b 相加的结果,赋值给变量 result”这一操作,并且已经去除了所有不影响语法的细节(如空格)。 将 AST(抽象语法树)转化为字节码的过程,是由 Python 的编译器 (Compiler) 完成的。这个过程可以被理解为一个**“翻译”**工作:编译器读取结构化的 AST,并将其翻译成一个线性的、供 Python 虚拟机 (PVM) 执行的指令列表。

这个“翻译”过程的核心思想是 “树的遍历” (Tree Traversal)。

第3步:遍历 AST 并生成指令

编译器会从 AST 的根节点开始,以深度优先的方式递归地访问(或称“遍历”)树中的每一个节点。对于每一种类型的节点(如 AssignBinOpName 等),编译器都有一个专门的处理函数。这个设计模式通常被称为 访问者模式 (Visitor Pattern)。

让我们以我们熟悉的 result = a + b 为例,看看编译器是如何“走”过它的 AST 并生成字节码的。

import ast# 我们要分析的简单代码行
code_line = """
# 这是一个我们编写的简单函数
result = a + b
"""# 解析代码并获取 AST
tree = ast.parse(code_line)# ast.dump() 可以用一种开发者友好的方式打印出树的结构
print(ast.dump(tree, indent=4))

这是它的 AST 结构:

Assign(targets=[Name(id='result')],value=BinOp(left=Name(id='a'),op=Add(),right=Name(id='b'))
)

编译器的工作流程如下:

  1. 访问 Assign (赋值) 节点

    • 编译器的规则是:要完成一次赋值,必须先计算出右边的值。

    • 因此,它不会立即生成赋值指令,而是递归地去访问 Assign 节点的 value 子节点,也就是 BinOp 节点。

  2. 访问 BinOp (二元运算) 节点

    • 编译器的规则是:要计算一个二元运算,必须先得到左右两个操作数的值。

    • 它首先递归地访问 left 子节点,也就是 Name(id='a')

  3. 访问 Name(id='a') 节点

    • 编译器看到这是一个变量名,并且当前上下文是需要“加载”它的值。

    • 于是,它生成第一条字节码指令:LOAD_FAST a。这条指令的作用是:在程序运行时,从本地变量中找到 a 的值,并将其推到 PVM 的操作数堆栈顶部。

    • a 节点处理完毕,返回到 BinOp 节点的访问流程。

  4. 回到 BinOp 节点

    • 左边已经处理完。现在,编译器递归地访问 right 子节点,也就是 Name(id='b')

  5. 访问 Name(id='b') 节点

    • 和处理 a 一样,编译器生成第二条字节码指令:LOAD_FAST b。运行时,b 的值会被推到堆栈顶部。

    • b 节点处理完毕,返回到 BinOp 节点的访问流程。

  6. 再次回到 BinOp 节点

    • 现在,左右两个子节点都处理完了。编译器知道,在运行时,a 和 b 的值会依次位于操作数堆栈上。

    • 它查看操作符 op 是 Add()

    • 于是,它生成第三条字节码指令:BINARY_ADD。这条指令会从堆栈弹出顶部的两个值,将它们相加,然后把结果再推回堆栈顶部。

    • BinOp 节点处理完毕,返回到 Assign 节点的访问流程。

  7. 再次回到 Assign 节点

    • 现在,value 子节点(即 a + b)已经完全处理完。编译器知道,在运行时,a + b 的计算结果正位于操作数堆栈的顶部。

    • 它查看赋值目标 targets 是 Name(id='result')

    • 于是,它生成第四条字节码指令:STORE_FAST result。这条指令会从堆栈顶部弹出那个计算结果,并将其存入名为 result 的本地变量中。

    • Assign 节点处理完毕。

至此,result = a + b 这行代码的 AST 就被完整地遍历并转换成了字节码序列:

LOAD_FAST     a
LOAD_FAST     b
BINARY_ADD
STORE_FAST    result

Part.2 解释执行

当字节码准备就绪后,就轮到 Python 虚拟机 (PVM) 登场了。PVM 是 Python 的运行时引擎,是真正执行代码的地方。它是一个在您的操作系统上运行的软件程序,它模拟了一台“理想”的计算机,专门用来执行 Python 字节码。

PVM 的核心可以理解为一个巨大的 switch 循环,我们称之为评估循环 (Evaluation Loop) 或解释器循环。这个循环不断地做三件事:

  1. 读取下一条字节码指令。

  2. 根据指令的类型,执行相应的操作。

  3. 重复此过程,直到没有指令为止。

PVM 是一个堆栈机 (Stack-based machine)。这意味着它使用一种叫做“调用堆栈 (Call Stack)”的数据结构来管理所有的数据和操作。当一个函数被调用时,PVM 会为这个函数创建一个新的帧 (Frame),并将其推入调用堆栈的顶部。

一个帧包含了执行该函数所需的所有信息,主要包括:

  • 本地变量 (Local variables): 存储函数内部定义的变量,如 ab 和 result

  • 操作数堆栈 (Operand Stack): 也叫评估堆栈 (Evaluation Stack),用于执行字节码指令的临时工作区。例如 BINARY_ADD 就是在这个堆栈上完成计算的。

  • 指令指针 (Instruction Pointer): 指向当前正在执行的字节码指令。

  • 返回地址: 指向调用该函数的代码位置,以便函数执行完毕后可以返回。

CPython解释器核心源码

https://github.com/python/cpython/blob/3.9/Python/ceval.c

       case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(tstate, PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }

        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }

        case TARGET(STORE_FAST): {
            PREDICTED(STORE_FAST);
            PyObject *value = POP();
            SETLOCAL(oparg, value);
            FAST_DISPATCH();
        }

留个坑

GIL、JIT、GC

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/94684.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/94684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MacOS Docker 安装指南

MacOS Docker 安装指南 引言 Docker 是一个开源的应用容器引擎,它允许开发者打包他们的应用以及应用的依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。Docker …

Cisco 3750X交换机更新到IOS 15.2后无法启动 提示:Boot process failed...

背景及故障现象 一台新购入的二手Cisco 3750X-48P,原机自带IOS软件版本为12.x,可以正常工作。 但将IOS版本升级到15.2之后,在启动过程中卡住。 第一次加载IOS软件时是正常的,提示: Loading "flash:/c3750e-uni…

Redis Redis 常见数据类型

Redis 提供了 5 种数据结构,理解每种数据结构的特点对于 Redis 开发运维非常重要,同时掌握每种数据结构的常见命令,会在使用 Redis 的时候做到游刃有余。 一、预备知识 官方文档:Commands | Docs (redis.io) 1、最核心的两个命令…

金融风控实战:Spring Boot + LightGBM 贷款预测模型服务化(超详细版)

金融风控实战:Spring Boot LightGBM 贷款预测模型服务化(超详细版)一、整体架构设计二、模型训练与优化1. 特征工程(Python)2. 模型评估与优化三、Spring Boot 服务实现1. 项目结构2. ONNX 模型服务3. 特征工程服务4.…

前端学习 7:EDA 工具

目录 EDA 工具 Design Ware Synopsys CoreTools 套件 VCS verdi Design Compiler EDA 工具 常用的EDA工具主要来自三家公司:Synopsys、Cadence和Mentor(已被Siemens收购)。EDA,全称电子设计自动化(Electronics …

windows有一个企业微信安装包,脚本执行并安装到d盘。

以下是将本地已有的企业微信安装包安装到D盘的完整PowerShell脚本&#xff0c;包含详细的错误处理和进度反馈&#xff1a; <# .SYNOPSIS使用本地企业微信安装包安装到D盘 .DESCRIPTION自动检测本地安装包&#xff0c;静默安装到指定目录支持.exe和.msi格式安装包 #># 强制…

[LVGL] 布局系统 lv_flex, lv_grid | 输入设备 lv_indev | union

第五章&#xff1a;布局系统&#xff08;lv_flex, lv_grid&#xff09; 欢迎回来&#xff01; 在第四章&#xff1a;样式&#xff08;lv_style&#xff09;中&#xff0c;我们掌握了如何通过色彩、字体和圆角等特性美化部件。当界面元素具备视觉吸引力后&#xff0c;如何优雅…

Linux中的mkdir命令

基本语法mkdir 命令的基本语法如下&#xff1a;mkdir [选项] 目录名创建单个目录要创建一个新目录&#xff0c;只需在 mkdir 后跟上目录名称。例如&#xff1a;mkdir new_folder这会在当前工作目录下创建一个名为 new_folder 的目录。创建多个目录可以一次性创建多个目录&#…

基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js

本文项目编号 25003 &#xff0c;文末自助获取源码 \color{red}{25003&#xff0c;文末自助获取源码} 25003&#xff0c;文末自助获取源码 目录 一、系统介绍二、系统录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状 六、核心代码6.1 查询数据6.2 新…

微信小程序精品项目-基于springboot+Android的计算机精品课程学习系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

(五)系统可靠性设计

2024年博主考软考高级系统架构师没通过&#xff0c;于是决定集中精力认真学习系统架构的每一个环节&#xff0c;并在2025年软考中取得了不错的成绩&#xff0c;虽然做信息安全的考架构师很难&#xff0c;但找对方法&#xff0c;问题就不大&#xff01; 本文主要是博主在学习过程…

Shuffle SOAR使用学习经验

Shuffle SOAR 1. 基础操作与配置1.1 环境搭建与系统要求1.1.1 硬件与操作系统要求Shuffle SOAR 平台作为一款开源的安全编排、自动化与响应&#xff08;SOAR&#xff09;工具&#xff0c;其部署方式灵活&#xff0c;支持云端和自托管两种模式。对于自托管部署&#xff0c;官方推…

腾讯云 EdgeOne 产品分析与免费套餐体验指南

本文围绕腾讯云 EdgeOne 展开&#xff0c;全方位介绍它的核心能力、免费套餐内容&#xff0c;以及如何快速上手、监控和排查常见问题&#xff0c;帮助个人开发者和中小企业在不产生额外成本的前提下体验高性能的边缘加速与安全防护。 一、产品概述 EdgeOne 定位 一体化云服务平…

npm ERR! Unsupported URL Type “workspace:“: workspace:./lib

如下 npm install npm ERR! code EUNSUPPORTEDPROTOCOL npm ERR! Unsupported URL Type "workspace:": workspace:./libnpm ERR! A complete log of this run can be found in: D:\IDEA\nodejs\node_cache\_logs\2025-08-06T08_21_32_592Z-debug-0.log原因及解决 pac…

微积分: 变化与累积

微积分,这门研究变化与累积的数学分支,其核心思想竟与东方哲学中"易"的概念不谋而合。《易经》有云:“易有太极,是生两仪”,而微积分正是通过"微分"与"积分"这对辩证统一的操作,揭示了世间万物变化与永恒的奥秘。 #mermaid-svg-UjO6qqMm0h…

web-vue工作流程

接续bmcweb流程。 当登录openbmc web页面后,浏览器会根据index.html中的js文件中的routes信息,自动获取信息,比如当前的网络设置信息、Datetime时区时间信息等。 以获取网络配置信息为例: 浏览器从app.js获取到settins->network的route:”/settings/network”,加载对应…

全球化2.0 | 泰国IT服务商携手云轴科技ZStack重塑云租赁新生态

在全球数字化转型不断加速的今天&#xff0c;泰国企业对于高质量云服务的需求日益旺盛。作为深耕本地市场逾二十年的行业领先IT服务商&#xff0c;泰国IT服务商不仅覆盖了IT系统、软件、硬件及网络等多个领域&#xff0c;还持续引领当地技术服务创新。近期&#xff0c;该泰国IT…

一文搞懂Hive临时表操作秘籍

Hive 临时表&#xff1a;数据处理的得力助手 在大数据处理的广阔领域中&#xff0c;Hive 凭借其强大的数据仓库功能&#xff0c;成为了众多数据分析师和开发者的得力工具。Hive 提供了类似 SQL 的查询语言 HiveQL&#xff0c;让我们能够方便地对存储在 Hadoop 分布式文件系统&a…

瞬态吸收光谱仪的基本原理

目录 1. 基态与激发态 2. 时间上的动力学信息 3. pump-probe探测技术 4. 时间延迟和同一光源 5. 延时线和OPA 6. 差分信号 7. 斩波器 原视频链接&#xff1a;瞬态吸收光谱仪的基本原理_哔哩哔哩_bilibili 1. 基态与激发态 当光照射在物质上时&#xff0c;组成物质的微观…

迭代器与生成器:Python 中的高效数据遍历机制

一、迭代器和生成器的基本概念 1. 迭代器的定义和工作原理 &#xff08;1&#xff09;迭代器的概念 迭代器&#xff08;Iterator&#xff09; 是 Python 中一种支持逐个访问元素的对象&#xff0c;它遵循 迭代器协议&#xff08;Iterator Protocol&#xff09;&#xff0c;即实…