Cpython翻译 ing,,教你阅读 Cpyth


教你阅读 Cpython 的源码

目录

第一部分-介绍 Cpython

源代码中有什么?
如何编译Cpython代码
编译器能做什么?
为什么 Cpython 是用C语言而是 Python 编写的?
Python语言的规范
Cpython 中的内存管理机制
结论

第二部分-Python 解释器进程

建立运行时配置
读取文件/输入
词法解析和句法解析
抽象语法树
结论

第三部分- Cpython 的编译器和执行循环

编译
执行
结论

第四部分-Cpython 中的对象

基础对象类型
Bool和Long Integer 类型
回顾Generator类型
结论

第五部分 Cpython标准库

Python 模块
Pyhton 和 C 模块
Cpython回归测试套件
安装用户自定C义版本

最后-Cpython 源代码:结论

在使用 Python 的过程中你是否有这些疑惑,使用字典查找内容,为什么比遍历一个列表要快得多?生成器如何在每次生成值时记住变量的状态?为什么使用Python时我们不用像其他语言那样分配内存?事实证明,CPython,是最流行的Python版本,运行时是用人类可读的C和Python代码编写的。

这篇文主要就是围绕着Cpython展开的,文章涵盖CPython内部原理背后的所有概念、它们的工作原理以及可视化的解释。
你将会学到的内容有:

学会阅读源码从源代码编译 CPython理解列表、字典和生成器等概念以及内部工作原理运行测试套件修改或升级 CPython 库的组件,或许在未来可以贡献 新的 Python 版本

这篇文章很长但是很有用,如果你决定要学习Cpython,那么希望你能看下去,你会发现这是一份不错的学习资料。
这篇文章总共分为5部分,你可以根据自己的时候合理的安排阅读时间。每一部分都要花一定的时间,通过自己去研究这里面的一些案例,你会感到一种成就感,因为你掌握了Python的核心概念,这使得你成为一名更好的Python程序员。

第一部分 介绍 Cpython

我们平时说的Python,其实大多都是指的Cpython,CPython是众多Python中的其中一种,初次之外还有Pypy,Jpython等。CPython 同样的作为官方使用的 Python版本,以及网上的众多案例。所以,这里我们主要说的是 Cpython。
注意:本文是针对CPython源代码的3.8.0b3版编写的。

源代码中有什么?

CPython源代码分发包含各种工具,库和组件。我们将在本文中探讨这些内容。
首先,我们将重点关注编译器。先从git上下载Cpython源代码.

git clone https://github.com/python/cpythoncd cpythongit checkout v3.8.0b3 #切换我们需要的分支

注意:如果你没有Git,可以直接从GitHub网站下载ZIP文件中的源代码。
解压我们下载的文件,其目录结构如下:

cpython/│├── Doc      ← 源代码文档说明├── Grammar  ← 计算机可读的语言定义├── Include  ← C语言头文件(头文件中一般放一些重复使用的代码)├── Lib      ← Python写的标准库文件├── Mac      ← Mac支持的文件├── Misc     ← 杂项├── Modules  ← C写的标准库文件├── Objects  ← 核心类型和对象模块├── Parser   ← Python 解析器源码├── PC       ← Windows 编译支持的文件├── PCbuild  ← 老版本的Windows系统 编译支持的文件├── Programs ← Python 可执行文件和其他二进制文件的源代码├── Python   ← CPython  解析器源码└── Tools    ← 用于构建或扩展 Python 的独立工具

接下来,我们将从源代码编译CPython。
此步骤需要C编译器和一些构建工具。不同的系统编译方法也不同,这里我用的是mac系统。

在macOS上编译CPython非常简单。在终端内,运行以下命令即可安装C编译器和工具包:

$ xcode-select --install

此命令将弹出一个提示,下载并安装一组工具,包括Git,Make和GNU C编译器。
你还需要一个 OpenSSL 的工作副本,用于从PyPi.org网站获取包。
如果你以后计划使用此版本来安装其他软件包,则需要进行SSL验证。
在macOS上安装OpenSSL的最简单方法是使用HomeBrew。
如果已经安装了HomeBrew,则可以使用brew install命令安装CPython的依赖项。

$ brew install openssl xz zlib

现在你已拥有依赖项,你可以运行Cpython目录下的configure脚本:

$ CPPFLAGS="-I$(brew --prefix zlib)/include"  LDFLAGS="-L$(brew --prefix zlib)/lib"  ./configure --with-openssl=$(brew --prefix openssl) --with-pydebug

上面的安装命令中,
CPPFLAGS 是 c 和 c++ 编译器的选项,这里指定了 zlib 头文件的位置,
LDFLAGS 是 gcc 等编译器会用到的一些优化参数,这里是指定了 zlib 库文件的位置,
(brew --prefix openssl) 这一部分的意思是在终端里执行括号里的命令,显示 openssl 的安装路径,可以事先执行括号里的命令,用返回的结果替换 (brew --prefix openssl),效果是一样的,每一行行尾的反斜杠可以使换行时先不执行命令,而是把这三行内容当作一条命令执行。

运行完上面命令以后在存储库的根目录中会生成一个Makefile,你可以使用它来自动化构建过程。./configure步骤只需要运行一次。
你可以通过运行以下命令来构建CPython二进制文件。

$ make -j2 -s

-j2标志允许make同时运行2个作业。如果你有4个内核,则可以将其更改为4. -s标志会阻止Makefile将其运行的每个命令打印到控制台。你可以删除它,输出的东西太多了。在构建期间,你可能会收到一些错误,在摘要中,它会通知你并非所有包都可以构建。
例如,_dbm,_sqlite3,_uuid,nis,ossaudiodev,spwd和_tkinter将无法使用这组指令构建。如果你不打算针对这些软件包进行开发,这个错误没什么影响。如果你实在需要可以参考:https://devguide.python.org/。
构建将花费几分钟并生成一个名为python.exe的二进制文件。每次改动源代码,都需要重新运行make进行编译。
python.exe二进制文件是CPython的调试二进制文件。执行下面命令可以看到Python的运行版本。

$ ./python.exePython 3.8.0b3 (tags/v3.8.0b3:4336222407, Aug 21 2019, 10:00:03) [Clang 10.0.1 (clang-1001.0.46.4)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> 

(其实最新的已经到Python3.9了,我编译了一下效果如下)
技术图片

编译器做了什么?

编译器的目的就是将一种语言转为另外一种语言。可以把编译的过程比作翻译,把英语里的“Hello”,翻译成中文的「你好」。

一些编译器将代码编译成只有机器看懂的机器代码,可以直接在系统上进行执行。其他编译器将编译成中间语言,由虚拟机执行。
选择编译器时做出的一个重要决定是系统可移植性要求。Java和.NET CLR将编译成中间语言,以便编译的代码可适配其他系统类型。C,Go,C ++和Pascal将编译成一个低级可执行文件,只能在类似于编译的系统上运行。
我们一般会直接发布Python的源代码,然后直接通过Python命令即可运行,其实在内部,运行时CPython会编译你的代码。大多数认为Python是一种解释性语言。
严格来说其实它实际上是编译类型。

Python代码不会编译成机器代码。
它被编译成一种特殊的低级中间语言,只有CPython才能理解的字节码。在Python3中字节码就存储在隐藏目录中的.pyc文件中,提供了缓存以供下次快速执行。所以,如果在不更改源代码的情况下运行相同的Python应用程序两次,第二次总是会快得多。原因就是第二次的时候直接加载了字节码然后运行了程序,不像第一次还需要编译。

为什么CPython是用C而不是Python编写的?

CPython中的C是对C编程语言的引用,暗示这个Python发行版是用C语言编写的。
CPython中的编译器是用纯C编写的。但是,许多标准库模块都是用纯Python或C和Python的组合编写的。

那么为什么CPython是用C而不是Python编写的?

答案就在于编译器的工作原理。
编译器有两种类型:

自托管编译器是用它们编译的语言编写的编译器,例如Go编译器。源到源编译器是用另一种已经有编译器的语言编写的编译器。
这也就意味着如果从头开始编写新的编程语言,则需要一个可执行的应用程序来编译你的编译器!你就需要一个编译器来执行任何操作,因此在开发新语言时,它们通常首先用较旧的,更成熟的语言编写。同时节省时间和学习成本。
一个很好的例子就是Go语言。
第一个Go编译器是用C编写的,然后Go可以编译,编译器就在Go中重写了。

CPython保留了它的C的特性:许多标准库模块(如ssl模块或sockets模块)都是用C语言编写的,用于访问低级操作系统API。
用于创建网络套接字,与文件系统一起工作或与显示器交互的Windows和Linux内核中的API都是用C语言编写的。所以将Python的可扩展性层专注于C语言是有意义的。在本文的后面部分,我们将介绍Python标准库和C模块。初次之外,有一个用Python编写的Python编译器叫做PyPy。
PyPy的徽标是一个Ouroboros,代表编译器的自托管特性。另一个Python交叉编译器的例子是Jython。

还有一个就是Jython。Jython是用Java编写的,从Python源代码编译成Java字节码。与CPython可以轻松导入C库并从Python中使用它们一样,Jython使得导入和引用Java模块和类变得容易。

Python语言规范

CPython源代码中包含的是Python语言的定义。这是所有Python解释器使用的参考规范。该规范采用人类可读和机器可读的格式。文档内部详细说明了Python语言,允许的内容以及每个语句的行为方式。

文档

位于Doc/reference目录内的是reStructuredText文件解释了Python语言中每个功能属性。这构成了docs.python.org上的官方Python参考指南。
在目录中是你需要了解整个语言,结构和关键字的文件:

cpython/Doc/reference|├── compound_stmts.rst├── datamodel.rst├── executionmodel.rst├── expressions.rst├── grammar.rst├── import.rst├── index.rst├── introduction.rst├── lexical_analysis.rst├── simple_stmts.rst└── toplevel_components.rst

在compound_stmts.rst文件中,你可以看到一个定义with语句的简单示例。with语句可以在Python中以多种方式使用,最简单的是上下文管理器的实例化和嵌套的代码块:

with x():   ...

你可以使用as进行重命名

with x() as y:   ...

你还可以链式的同时定义多个

with x() as y, z() as jk:   ...

接下来,我们将探索Python语言的计算机可读文档。

Grammar

该文档包含人类可读规范和存放在单个文件Grammar/Grammar中的机器可读规范。
Grammar文件是使用称为Backus-Naur Form(BNF)的上下文表示法进行编写的。
BNF不是特定于Python的,并且通常用作许多其他语言中语法的符号。
编程语言中的语法结构概念是从20世纪50年代Noam Chomsky’s work on Syntactic Structures中受到启发的。
Python的语法文件使用具有正则表达式语法的Extended-BNF(EBNF)规范。
所以,在语法文件中你可以使用:

*重复+至少重复一次[]为可选部分|任选一个()用于分组
如果在语法文件中搜索with语句,你将看到with语句的定义:
.. productionlist::   with_stmt: "with" `with_item` ("," `with_item`)* ":" `suite`   with_item: `expression` ["as" `target`]

引号中的内容都是字符串,这是一中关键字的定义方式。所以with_stmt指定为:
1.with单词开头
2.接下来是with_item,它是一个test和(可选)as表达式。
3.多个项目之间使用逗号进行间隔
4.以字符:结尾
5.其次是suite。
这两行中提到了一些其他定义:

suite是指具有一个或多个语句的代码块。test是指一个被评估的简单语句。expr指的是一个简单的表达式
如果你想详细探索这些内容,可以在此文件中定义整个Python语法。

如果你想看一个最近如何使用语法的例子,例如在PEP572中,:=运算符被添加到语法文件中。

  ATEQUAL                 '@='  RARROW                  '->'  ELLIPSIS                '...'+ COLONEQUAL              ':='  OP  ERRORTOKEN

使用pgen

Grammar文件本身不会被Python编译器使用。
是使用一个名为pgen的工具,来创建的解析器表。pgen会读取语法文件并将其转换为解析器表。如果你对语法文件进行了更改,则必须重新生成解析器表并重新编译Python。

注意:pgen应用程序在Python 3.8中从C重写为纯Python。

为了查看pgen的运行情况,让我们改变Python语法的一部分。并重新编译运行Python。
在Grammar路径下看到两个文件Grammar 和Tokens,我们在Grammar搜索pass_stmt,然后看到下面这样

pass_stmt: 'pass'

我们修改一下,改为下面这样

pass_stmt: 'pass' | 'proceed'

在Cpython的根目录使用make regen-grammar命令来运行pgen重新编译Grammar文件。
应该看到类似于此的输出,表明已生成新的Include/graminit.h和Python/graminit.c文件:
下面是部分输出内容

# Regenerate Include/graminit.h and Python/graminit.c# from Grammar/Grammar using pgenPYTHONPATH=. python3 -m Parser.pgen ./Grammar/Grammar         ./Grammar/Tokens         ./Include/graminit.h.new         ./Python/graminit.c.newpython3 ./Tools/scripts/update_file.py ./Include/graminit.h ./Include/graminit.h.newpython3 ./Tools/scripts/update_file.py ./Python/graminit.c ./Python/graminit.c.new

使用重新生成的解析器表,需要重新编译CPython才能查看新语法。使用之前用于操作系统的相同编译步骤。

make -j4 -s

如果代码编译成功,执行新的CPython二进制文件并启动REPL。

./python.exe

在REPL中,现在可以尝试定义一个函数,使用编译为Python语法的proceed关键字替代pass语句。

Python 3.8.0b3 (tags/v3.8.0b3:4336222407, Aug 21 2019, 10:00:03) [Clang 10.0.1 (clang-1001.0.46.4)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> def example():...    proceed... >>> example()

下面是我运行结果,很有意思居然没有出错。
技术图片
接下来,我们将探讨Tokens文件及其与Grammar的关系。

Tokens

与Grammar文件夹中的语法文件一起是一个Tokens文件,它包含在解析树中作为叶节点找到的每个唯一类型,稍后我们将深入介绍解析器树。每个token还具有名称和生成的唯一ID,这些名称用于简化在tokenizer中引用。

注意:Tokens文件是Python 3.8中的一项新功能。

例如,左括号称为LPAR,分号称为SEMI。
你将在本文后面看到这些标记:

LPAR                    '('RPAR                    ')'LSQB                    '['RSQB                    ']'COLON                   ':'COMMA                   ','SEMI                    ';'

与语法文件一样,如果更改Tokens文件,则需要再次运行pgen。
要查看操作中的tokens,可以在CPython中使用tokenize模块。创建一个名为test_tokens.py的简单Python脚本:

# Hello world!def my_function():   proceed

然后通过名为tokenize的标准库中内置的模块传递此文件。你将按行和字符查看令牌列表。使用-e标志输出确切的令牌名称:

0,0-0,0:            ENCODING       'utf-8'        1,0-1,14:           COMMENT        '# Hello world!'1,14-1,15:          NL             '\n'           2,0-2,3:            NAME           'def'          2,4-2,15:           NAME           'my_function'  2,15-2,16:          LPAR           '('            2,16-2,17:          RPAR           ')'            2,17-2,18:          COLON          ':'            2,18-2,19:          NEWLINE        '\n'           3,0-3,3:            INDENT         '   '          3,3-3,7:            NAME           'proceed'         3,7-3,8:            NEWLINE        '\n'           4,0-4,0:            DEDENT         ''             4,0-4,0:            ENDMARKER      ''   

技术图片
在输出中,第一列是行/列坐标的范围,第二列是令牌的名称,最后一列是令牌的值。
在输出中,tokenize模块隐含了一些不在文件中的标记。
utf-8的ENCODING标记,末尾有一个空行,DEDENT关闭函数声明,ENDMARKER结束文件。tokenize模块是用纯Python编写的,位于CPython源代码中的Lib/tokenize.py中。

重要提示:CPython源代码中有两个tokenizers:一个用Python编写,上面演示的这个,另一个是用C语言编写的。用Python编写的被用作实用程序,而用C编写的被用于Python编译器。但是,它们具有相同的输出和行为。用C语言编写的版本是为性能而设计的,Python中的模块是为调试而设计的。

要查看C语言的的tokenizer的详细内容,可以使用-d标志运行Python。
使用之前创建的test_tokens.py脚本,使用以下命令运行它:

./python.exe -d test_tokens.py

得到如下结果

Token NAME/'def' ... It's a keyword DFA 'file_input', state 0: Push 'stmt' DFA 'stmt', state 0: Push 'compound_stmt' DFA 'compound_stm![](https://img2018.cnblogs.com/blog/736399/201908/736399-20190825011800182-564889061.jpg)t', state 0: Push 'funcdef' DFA 'funcdef', state 0: Shift.Token NAME/'my_function' ... It's a token we know DFA 'funcdef', state 1: Shift.Token LPAR/'(' ... It's a token we know DFA 'funcdef', state 2: Push 'parameters' DFA 'parameters', state 0: Shift.Token RPAR/')' ... It's a token we know DFA 'parameters', state 1: Shift.  DFA 'parameters', state 2: Direct pop.Token COLON/':' ... It's a token we know DFA 'funcdef', state 3: Shift.Token NEWLINE/'' ... It's a token we know DFA 'funcdef', state 5: [switch func_body_suite to suite] Push 'suite' DFA 'suite', state 0: Shift.Token INDENT/'' ... It's a token we know DFA 'suite', state 1: Shift.Token NAME/'proceed' ... It's a keyword DFA 'suite', state 3: Push 'stmt'...  ACCEPT.

在输出中,您可以看到它突出显示为关键字。在下一章中,我们将看到如何执行Python二进制文件到达tokenizer以及从那里执行代码会发生什么。现在您已经概述了Python语法以及tokens和语句之间的关系,有一种方法可以将pgen输出转换为交互式图形。
以下是Python 3.8a2语法的屏幕截图:
技术图片

Cpython翻译 ing

评论关闭