首頁 >後端開發 >Python教學 >口譯員中的口譯員

口譯員中的口譯員

Susan Sarandon
Susan Sarandon原創
2024-12-15 20:22:11127瀏覽

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