CPython 实现原理
  • README
  • 一、简介
    • 1.1 如何使用此书
    • 1.2 额外材料和学习资料
  • 二、获取 CPython 源码
    • 2.1 源代码里有什么?
  • 三、准备你的开发环境
    • 3.1 选IDE还是编辑器?
    • 3.2 安装Visual Studio
    • 3.3 安装Visual Studio Code
    • 3.4 安装JetBrains Clion
    • 3.5 安装Vim
    • 3.6 总结
  • 四、编译 CPython
    • 4.1 在 macOS 上编译 CPython
    • 4.2 在 Linux 上编译 CPython
    • 4.3 安装自定义版本
    • 4.4 make 快速入门
    • 4.5 CPython 的 make 目标
    • 4.6 在 Windows 上编译 CPython
    • 4.7 PGO 优化
    • 4.8 总结
  • 五、Python 语言和语法
    • 5.1 为什么 CPython 是用 C 语言而不是用 Python 语言来实现
    • 5.2 Python 语言规范
    • 5.3 分析器生成器
    • 5.4 重新生成语法
    • 5.5 总结
  • 六、配置和输入
    • 6.1 配置状态
    • 6.2 构建配置
    • 6.3 从输入构建模块
    • 6.4 总结
  • 七、基于语法树的词法分析和解析
    • 7.1 具象语法树生成器
    • 7.2 CPython 解析器-分词器
    • 7.3 抽象语法树
    • 7.4 要记住的术语
    • 7.5 一个示例:添加一个约等于比较运算法
    • 7.6 总结
  • 八、编译器
    • 8.1 相关源文件
    • 8.2 重要的专业术语
    • 8.3 实例化一个编译器
    • 8.4 未来标志和编译器标志
    • 8.5 符号表
    • 8.6 核心编译过程
    • 8.7 汇编
    • 8.8 创建一个 Code Object
    • 8.9 使用 Instaviz 展示 Code Object
    • 8.10 一个示例:实现约等于操作符
    • 8.11 总结
  • 九、求值循环
    • 9.1 构建线程状态
    • 9.2 构建帧对象
    • 9.3 帧的执行
    • 9.4 值栈
    • 9.5 例子:在列表中添加元素
    • 9.6 总结
  • 十、内存管理
    • 10.1 C 中的内存分配
    • 10.2 Python 内存管理系统设计
    • 10.3 CPython 内存分配器
  • 十一、并行和并发
    • 11.1 并行和并发模型
    • 11.2 进程的结构
    • 11.3 多进程并行
    • 11.4 多线程
    • 11.5 异步编程
    • 11.6 生成器
    • 11.7 协程
    • 11.8 异步生成器
    • 11.9 子解释器
    • 11.10 总结
  • 十二、对象和类型
    • 12.1 本章的例子
    • 12.2 内置类型
    • 12.3 对象和可变长度对象类型
    • 12.4 类型类
    • 12.5 布尔和整数类型
    • 12.6 Unicode 字符串类型
    • 12.7 字典类型
    • 12.8 总结
  • 十三、标准库
    • 13.1 Python 模块
    • 13.2 Python 和 C 模块
  • 十四、测试套件
    • 14.1 在 Windows 上运行测试套件
    • 14.2 在 Linux 或 MacOS 上运行测试套件
    • 14.3 测试标志
    • 14.4 运行特定测试
    • 14.5 测试模块
    • 14.6 测试工具
    • 14.7 总结
  • 十五、调试
  • 十六、基准测试、性能分析和追踪
  • 十七、下一步计划
    • 17.1 为 CPython 编写 C 扩展
    • 17.2 改进你的 Python 应用程序
    • 17.3 为 CPython 项目做贡献
    • 17.4 继续学习
  • 十八、附录
    • 18.1 C 预处理器
    • 18.2 基础 C 语法
    • 18.3 总结
  • 致谢
Powered by GitBook
On this page
Edit on GitHub
  1. 七、基于语法树的词法分析和解析

7.5 一个示例:添加一个约等于比较运算法

Previous7.4 要记住的术语Next7.6 总结

Last updated 2 years ago

如果要将本章节所有内容串联起来,你可以在 Python 语言中添加一个新的语法特性并重新编译 CPython 来掌握本章节内容。

比较表达式会比较两个或多个值:

在比较表达式中用到的运算符被称为比较运算符。以下可能是一些你能认出来的比较运算符:

  • 小于:<;

  • 大于:>;

  • 等于:==;

  • 不等于:!=。

参见

现在,让我们来添加另外一个比较运算符:约等于,由 ~= 表示,其将拥有如下行为:

  • 如果你比较一个浮点数和一个整数,那么它会将浮点数转换为整数并比较结果;

  • 如果你比较两个整数,那么它会使用普通的相等运算符。

新的运算符会在交互式解释器中返回如下结果:

为了添加新的运算符,首先你需要更新 CPython 语法。在 Grammar/python.gram 文件中,比较运算符被定义为一个符号(symbol):comp_op :

代码不翻译

修改 compare_op_bitwise_or_pair 表达式以允许新的 ale_bitwise_or 对:

代码不翻译

在现有的 is_bitwise_or 表达式下定义新的 ale_bitwise_or 表达式:

...
is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or ...
ale_bitwise_or[CmpopExprPair*]: '~=' a=bitwise_or
    { _PyPegen_cmpop_expr_pair(p, AlE, a) }

这个新的类型定义了一个包含 '~=' 终结符号的命名表达式:ale_bitwise_or。

函数调用 _PyPegen_cmpop_expr_pair(p, AlE, a) 是一个从 AST 获取 cmpop 节点的表达式。类型为 AlE:Almost Equal。

接下来,在 Grammar/Tokens 中添加单词符号(token):

ATEQUAL '@='
RARROW '->'
ELLIPSIS '...'
COLONEQUAL ':='
# Add this line
ALMOSTEQUAL '~='

为了更新 C 语言中的语法和单词符号,你需要重新生成头文件。

在 MacOS 或 Linux上使用以下命令:

$ make regen-token regen-pegen

在 Windows 上运行 PCBuild 目录中的以下命令:

> build.bat --regen

如果你在这个阶段重新编译 CPython 并且打开一个交互式解释器,那么你可以看到分词器已经可以成功识别到这个单词符号,但是此时 AST 还不知道如何处理它:

Compare 是一个定义在 Parser/Python.asdl 文件中的一个表达式类型,其具有的属性:左表达式、操作符(ops)列表以及一个要比较的表达式列表(comparators):

在 Compare 定义中是对 cmpop 枚举的引用:

这是一个可以充当比较运算符的抽象语法树叶子节点的列表集合。我们定义的约等于运算符没在里面所以需要补充完整。更新这个选项集合来包含一个新类型:ALE:

接下来,我们重新生成 AST 来更新 AST 的 C 头文件:

这个操作会更新 Include/Python-ast.h 文件中的比较运算符(_cmpop)枚举,来包含 ALE 选项:

抽象语法树此时还没有能力将 ALMOSTEQUAL 单词符号和 ALE 比较运算符关联上。所以你还要为抽象语法树更新 C 语言代码。

添加两行代码,用于捕捉 ALMOSTEQUAL 单词符号并返回 AlE 比较运算符:

在这个阶段,分词器和 AST 可以解析代码,但是编译器还是不知道如何处理这个运算符。为了测试抽象语法树的表示,我们可以使用 ast.parse() 函数来探索表达式的第一个运算符:

这是我们 AlE 比较运算符类型的一个实例,因此抽象语法树才能正确的分析代码。

在下一个章节,你会继续学习 CPython 编译器是如何工作的,并继续修改约等于运算符来构建其执行逻辑。

为 Python 2.1 提出了在数据模型中的丰富比较运算符。此 PEP 包含了自定义 Python 类型实现比较方法的上下文、历史以及解释。

这些步骤会自动更新分词器(tokenizer)。比如:打开 Parser/token.c 源文件,你可以看到 函数已经被修改:

这个异常是由 Python/ast.c 文件中的 函数引起,原因是交互式解释器还无法把 ALMOSTEQUAL 识别为一个比较运算符中的一个有效运算符。

找到 Python/ast.c 文件中的 函数并找到运算符单词符号的 switch 语句。这个函数会返回 _cmop 枚举值中的一个。

PEP 207
PyToken_TwoChars()
ast_for_comp_op()
ast_for_comp_op()