首页 >后端开发 >Python教程 >口译员中的口译员

口译员中的口译员

Susan Sarandon
Susan Sarandon原创
2024-12-15 20:22:11121浏览

An interpreter inside an interpreter

开发几个月后,我决定孟菲斯的北极星是完全在我的解释器中运行 Flask 服务器。我不知道这需要做多少工作,只知道这听起来很酷,而且可能会教会我很多东西。如果我今天要实现这个目标,我可能会选择 FastAPI 或什么都不选择,因为那对我来说很愚蠢。

Python 标准库

我遇到的一个重大决定是如何处理Python标准库。正如您可能熟悉的那样,语言的标准库在技术上并不是语言定义或运行时的一部分。它包含在版本中,以使语言和运行时更有用。想象一下没有线程或异步支持的 Python。您仍然能够计算表达式并实例化类,但大多数生产就绪程序需要某种并发支持。

一种选择是自己重写整个标准库。我正在构建一个解释器,不是吗?我相信这就是RustPython所采取的方法,这是一条令人钦佩的道路。我认为我已经有足够的时间让运行时正常工作,正在寻找任何和所有的捷径,并决定不这样做。

Python 标准库由两个主要部分组成:用 Python 实现的部分和用 C 实现的部分。很方便的是,我有自己的 Python 解释器。我可以直接从主机解释Python源文件来满足前者吗?是的,我可以。我需要支持他们使用的每一个语法和功能,但在那之后,它就可以工作了。

C 部分是它变得有趣的地方。早在 2023 年,我就决定在我的 Python 解释器中嵌入一个 Python 解释器,但我并没有完全理解这意味着什么。现在是时候思考这个问题并决定是否要继续采用这种方法还是选择另一条路。

Rust 和 Python 的互操作商店是 Pyo3。作为镇上唯一的游戏,Pyo3 使用外部函数接口 (FFI) 来允许 Rust 代码调用 CPython 二进制文件。这是通过同意应用程序二进制接口 (ABI) 来实现的,这是我在 AMD 的职业生涯中使用的一个概念。核心软件ftw!

导入模块

我最初的用例是运行 import sys 并让它给我一个可以执行成员访问操作的对象。我在这里进入口译员讲话,但这就是我正在谈论的 REPL 会话类型。

Python 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> type(sys.modules)
<class 'dict'>

使用 Pyo3 获取此功能非常简单。

pub struct CPythonModule(PyObject);

impl CPythonModule {
    pub fn new(name: &str) -> Self {
        pyo3::prepare_freethreaded_python();
        let pymodule = Python::with_gil(|py|
            PyModule::import(py, name).expect("Failed to import module").into()
        );

        Self(pymodule)
    }
}

我们可以使用它在孟菲斯驱动一个类似的 REPL 会话,假设您还记得让它运行的功能标志鸡尾酒。

Python 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> type(sys.modules)
<class 'dict'>

如果你问自己,难道你不能用这种方式导入整个标准库(包括用Python和C编写的部分),让你的整个生活、自由和幸福的追求变得更容易吗?答案是肯定的。这将是一个有效的方法!然而,这会让我的解释器更像是 CPython 的外壳,超出了我的预期。这是一个学习练习,所以我完全赞成任意决定。对于那些说在 Memphis 中加载任何 CPython 片段都会导致 Memphis 不是真正的解释器的纯粹主义者,我只想说:请向我展示你的解释器。

我使用 Memphis 和 CPython 在 REPL 会话中运行 import sys,对 htop 进行了快速测试。在 Memphis 上,由于这会将 CPython 库加载到内存中,因此 RAM 使用量(htop 中的驻留集大小)增加了约 5MB。作为比较,Memphis REPL 加载 sys 模块之后使用大约 9MB 的 RAM,而 Python REPL 加载 sys 模块之前和之后使用的 RAM 大约相同。我确信这不是同类比较,但它至少告诉我,孟菲斯不会慢慢地让我的电脑窒息而死。

转换对象并获得存在性

此设置的下一个复杂性涉及将我的 Memphis 对象表示形式转换为 CPython 表示形式,反之亦然。这是一项正在进行的工作,我的主要指令最初是“不要失败”,最近是“在进行有损转换时显示警告。”

这是我从 PyObject(Pyo3 端的对象表示)到 ExprResult(我的孟菲斯表示)的转换。

pub struct CPythonModule(PyObject);

impl CPythonModule {
    pub fn new(name: &str) -> Self {
        pyo3::prepare_freethreaded_python();
        let pymodule = Python::with_gil(|py|
            PyModule::import(py, name).expect("Failed to import module").into()
        );

        Self(pymodule)
    }
}

这是反向比较。请注意,对于这两者,我们必须传入一个 Python 对象,该对象控制我们对 CPython GIL(全局解释器锁)的访问。

memphis 0.1.0 REPL (Type 'exit()' to quit)
>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> type(sys.modules)
<class 'dict' (built-in)>

这是一个我想进一步探索的丰富领域。以下是我考虑过的一些方向:

  1. 每次对象穿过 FFI 接口时都会进行转换。 (是的,我意识到缩写词扩展为外部函数接口接口。)这大致就是我已经在做的事情,我只需要拥有它而不是感觉自己像个冒名顶替者。这可能很简单,但效率很低。
  2. 保留一个注册表,以便每个对象在每一侧最多存在一次。这比 (1) 更有效,但它需要一个稳定的值,您可以使用它来查找和链接这些对象。
  3. 以 Rust 端的单一表示为目标,并根据需要使用 Pyo3 来代理和延迟转换字段。我相信这仍然会利用 (1) 的功能,但会以更有效的方式。
  4. 使 Memphis 对象的内存布局与 PyObject 的内存布局相匹配。与 #[repr(C)] 在 Rust 中的工作方式类似,这类似于 ABI 在函数调用中扮演的角色。鉴于双方需要进行评估的内容有所不同,我什至不确定这是否可能,但这引起了我的兴趣。

我有点超前了,因为我现在几乎无法加载 C 模块,但我的好奇心在这个领域确实没有尽头。

结局

当我在缓慢启动 Flask 的过程中遇到新的转换失败时,我继续对此进行研究。这个练习很好地提醒我们,所有对象(或类、模块等)都是一组以已知格式存在于内存中的属性。如果我们充分理解这种格式,我们应该能够做出令人难以置信的事情,无论是在 Memphis 还是 CPython 方面。

这一理念也推动了我对 From Scratch Code 的工作。如果您厌倦了无法让库在您的代码中工作,我鼓励您退后一步问:该库实际上在做什么?您是否需要它,或者可以使用更简单的解决方案吗?我相信培养对软件的好奇心,并且我很乐意帮助您将这种思维方式融入您的工具箱中。


如果您想将更多类似的帖子直接发送到您的收件箱,您可以在这里订阅!

别处

除了指导软件工程师之外,我还写了我作为一名成人诊断自闭症患者的经历。更少的代码和相同数量的笑话。

  • 为什么我渴望得到认可? - 来自 Scratch 点组织

以上是口译员中的口译员的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn