如何为 Python 新增语法

Python 是一套语法规范,规定了开发者如何编写 Python 的代码。如何解析、执行 Python 的源码,最后输出则是 Python 解释器的职责。我们平时使用的 Python 一般指的是 CPython,其解释器是由 C 语言编写。除此之外,还有比如 Jython, 使用 Java 编写的。Pypy 则是用 Python 写的。

这篇文章描述了如何获取 Python 的源码,源码的目录结构以及如何重新编译 Python。这篇文章使用的 Python 版本是 3.9,如果你想重现文章中的实验,最好是采用相同的版本。

获取源码

我们可以通过 git 来获取 CPython 的源码:

bash 复制代码
git clone --branch 3.9 <https://github.com/python/cpython>

项目使用的是 IDE 是 JetBrain CLion,开发环境采用的是 macOS。你可以使用任何你熟悉的编辑环境。

源码的目录结构

获取源码之后,我们首先熟悉一下源码的目录结构:

  • Doc:文档目录
  • Grammer:用于定义 Python 语法规则,计算机可以直接读取和处理的文件
  • Include:C 的头文件(Header files)
  • Lib:使用 Python 写的标准库模块
  • Mac:macOS 系统支持的文件
  • Misc:杂七杂八的文件,没办法分类的文件
  • Modules:使用 C 语言编写的标准库模块
  • Objects:核心类型和对象模型
  • Parser:Python 解析器的源码,语法分析,生成语法树或AST
  • PC:用于旧版本的 Windows 系统的构建的支持文件
  • PCbuild:用于新版本的 Windows 系统的构建的支持文件
  • Programs:Python 可执行文件和其他二进制文件的源码
  • Python:CPython 解释器的源码,解释执行 Python 的源码
  • Tools:用于构建和扩展 CPython 的一些独立的工具
  • m4: 用于配置 Makefile 的一些自定义脚本

编译 CPython

首先安装编译的依赖:

复制代码
 brew install openssl xz zlib gdbm sqlite

我以 macOS 为例,首先执行 ./configure 检查环境并生成 Makefile :

ini 复制代码
$ cpython % CPPFLAGS="-I$(brew --prefix zlib)/include" \
		LDFLAGS="-L$(brew --prefix zlib)/lib" \
		./configure --with-openssl=$(brew --prefix openssl) --with-pydebug

我来解释一下:

  • CPPFLAGS 是告诉 C 预处理器,zlib 库的 Header files 的存放的目录;
  • LDFLAGS 是在链接阶段,去 $(brew --prefix openssl) 所在目录查找库文件(一般以 *.so 或者 .a 作为扩展名;
  • --with-pydebug 可以让你在开发或者测试过程中启用调试。

不出意外,你可以看到如下这般输出:

lua 复制代码
config.status: creating pyconfig.h
creating Modules/Setup.local
creating Makefile

这表明你当前的系统符合编译要求,并且已经生成了编译所需的 Makefile 文件。可以进行编译了。

go 复制代码
make -j14 -s

这条命令中,-j14 表示通过 14 个任务并行编译(一般等于你的 CPU 核心数就行),-s全称是 --silent 表示不打印执行的命令本身,让输出更加简洁。

不出意外,你可以看到如下的输出,表明已经编译成功了:

复制代码
Python build finished successfully!

你会在源码的根目录下看一个 python.exe 的可执行文件。虽然扩展名为 .exe ,但这并不是 Windows 下的可执行文件,而是 Python 的开发者特意加上这个扩展名和源码目录下的 Python 目录做区分,因为在 macOS 系统中并不区分大小写,所以加上 .exe 避免歧义。

你可以执行执行这个可执行文件,输出编译的 Python 的版本:

shell 复制代码
$ ./python.exe --version
Python 3.9.22+

修改 Python 源码

接下来,我们尝试修改 Python 的源码。为 Python 增加一个关键字。我们知道,Python 中,如果你想暂时不实现一个方法,可以通过 pass 关键字,例如:

python 复制代码
def do_somethings():
	pass

我希望当我输入 pass 或者 ps 都能实现同样的效果。这就需要修改 Python 的语法定义。在 Grammar 目录下,找到 python.gram 文件,并且搜索 small_stmt 关键字,你会看到如下语句:

scss 复制代码
small_stmt[stmt_ty] (memo):
    | assignment
    | e=star_expressions { _Py_Expr(e, EXTRA) }
    | &'return' return_stmt
    | &('import' | 'from') import_stmt
    | &'raise' raise_stmt
    | 'pass' { _Py_Pass(EXTRA) }

更改如下,将 'pass' 更改为 ('pass' | 'ps') , 表明 pass 或者 ps 都可以:

scss 复制代码
- | 'pass' { _Py_Pass(EXTRA) }
+ | ('pass' | 'ps') { _Py_Pass(EXTRA) }

修改完成之后,执行如下命令重新生成语法解析器的表:

bash 复制代码
$ make regen-pegen
PYTHONPATH=./Tools/peg_generator python3 -m pegen -q c \
                ./Grammar/python.gram \
                ./Grammar/Tokens \
                -o ./Parser/pegen/parse.new.c
python3 ./Tools/scripts/update_file.py ./Parser/pegen/parse.c ./Parser/pegen/parse.new.c

然后我们重新编译 Python:

go 复制代码
make clean && make -j14 -s

最后测试一下:

总结

这篇文章描述了如何在 macOS 上编译 CPython 的源码,最终你会得到一个 python.exe 的可执行文件。

在了解了如何编译 Python 解释器之后,我们修改了 Python 的语法规则,增加了一个 ps 的关键字,来实现和 pass 关键字一样的功能,最后编译并测试。

相关推荐
OperateCode几秒前
AutoVideoMerge:让二刷更沉浸的自动化视频处理脚本工具
python·opencv·ffmpeg
蔡俊锋1 分钟前
Javar如何用RabbitMQ订单超时处理
java·python·rabbitmq·ruby
跟橙姐学代码8 分钟前
学Python别死记硬背,这份“编程生活化笔记”让你少走三年弯路
前端·python
顾林海1 小时前
Android MMKV 深度解析:原理、实践与源码剖析
android·面试·源码阅读
站大爷IP1 小时前
Python与MySQL:从基础操作到实战技巧的完整指南
python
老歌老听老掉牙1 小时前
SymPy 矩阵到 NumPy 数组的全面转换指南
python·线性代数·矩阵·numpy·sympy
站大爷IP1 小时前
Python条件判断:从基础到进阶的实用指南
python
赛博郎中1 小时前
pygame小游戏飞机大战_8继承精灵玩家优化
python·pygame
William一直在路上2 小时前
Python数据类型转换详解:从基础到实践
开发语言·python
trayvontang2 小时前
Python虚拟环境与包管理工具(uv、Conda)
python·conda·uv·虚拟环境·miniconda·miniforge