與 CPython 的差異
SteelPython 遵循 Python 的參考實現 CPython 3.12。除非本頁另有說明,官方文件 同樣適用於 SteelPython;大多數程式以及 PyPI 上的大多數包都可以不加修改地執行。
缺少的內容
| 缺少的內容 | 替代方案 |
|---|---|
tkinter、turtle、IDLE(idlelib) |
沒有 Tk。請使用 Misp 作為編輯器;如需自己的視窗,可通過 clr 使用 .NET UI 庫,或使用 streamlit 之類的 Web UI。 |
子直譯器(_xxsubinterpreters、_xxinterpchannels) |
使用執行緒(會並行執行,見下文)或 multiprocessing。 |
test 包(CPython 自己的測試套件) |
不隨附。 |
._pth 檔案 |
設定 PYTHONPATH 或 SPYPATH,或使用虛擬環境。 |
大於 2 GiB 的單個 bytes 物件 |
更大的檔案、記憶體對映和 memoryview 都可以正常使用;只是單個 bytes 物件(例如對 3 GB 檔案執行 mmap_object[:])不能超過 2 GiB。請分片處理。 |
使用者級 site 目錄(pip install --user)是 SteelPython 自己的:%APPDATA%\SteelPython\Python312\site-packages,而不是 CPython 的 %APPDATA%\Python\Python312,因此一方安裝的包永遠不會干擾另一方。
curses 和 os.fork() 同樣缺失,這與 Windows 上的 CPython 一致;multiprocessing 使用 spawn 方式啟動工作程序,也與 Windows 上的 CPython 一致。
執行緒並行執行
SteelPython 沒有全域直譯器鎖(GIL)。Python 執行緒會同時在不同的核心上執行,因此拆分到 ThreadPoolExecutor 上的 CPU 密集型工作確實會變快,就像在 CPython 3.13 及更高版本的自由執行緒構建上一樣。
所提供的保證也與那裡相同:對內建物件的單個操作(list.append、dict[key] = value、對共享迭代器呼叫 next() 等)是原子的,任何資料競爭都不會導致直譯器崩潰或破壞其狀態。複合操作——對共享變數執行 counter += 1、先檢查後操作的序列——需要使用 threading.Lock,這與 Python 一直以來的要求完全相同。在 CPython 上正確的程式碼在 SteelPython 上也是正確的;而那些暗中依賴 GIL 來序列化複合操作的程式碼,現在可能會暴露出競爭問題。
物件何時被釋放
CPython 會在物件的最後一個引用消失的那一刻釋放它。SteelPython 的物件則由 .NET 垃圾回收器釋放,回收器會不定期執行。這隻對那些觀察釋放時機的程式碼有影響:
__del__方法、weakref回撥和weakref.finalize會在回收器發現物件不可達時執行,或在下一次gc.collect()時執行,而不是在最後一次del時執行。它們確實會執行,只執行一次,並且在 Python 執行緒上執行。- 你沒有關閉的檔案和其他資源會在被回收時關閉(併發出通常的
ResourceWarning)。在常見情況下——檔案被開啟到區域變數中或作為臨時物件使用,生成器或協程僅被區域變數持有——SteelPython 會在該引用消失時立即釋放物件,就像 CPython 一樣。在其他所有情況下,請顯式關閉資源,這本來也是 Python 自己的文件所推薦的做法:使用with open(...) as f:。 sys.getrefcount()對普通物件不會報告真實的計數,gc.collect()返回的是它執行的終結器數量,而不是 CPython 的不可達物件數量。依據引用計數做決策的程式碼(在測試套件之外很少見)會表現得好像每個物件都是共享的。
編譯檔案與位元組碼
SteelPython 將編譯後的模組快取在 __pycache__ 中,儲存為名為 module.steelpython-312.pyc 的 .pyc 檔案。它們與 CPython 的 module.cpython-312.pyc 並存而不衝突,但兩種實現都無法讀取對方的檔案。
SteelPython 不執行 CPython 位元組碼。dis 顯示的是 CPython 針對同一原始碼會生成的位元組碼,因此檢查程式碼物件的工具可以正常工作;而且可以從 SteelPython 編譯的函式中複製和重建程式碼物件(code.replace()、types.CodeType(...))。由手寫位元組碼或由 CPython 生成的位元組碼組裝而成的程式碼物件,在被呼叫時會引發 SystemError。程式碼物件的 marshal 使用 SteelPython 自己的格式。
識別直譯器
| SteelPython | CPython | |
|---|---|---|
sys.implementation.name |
'steelpython' |
'cpython' |
sys.implementation.cache_tag |
'steelpython-312' |
'cpython-312' |
sys.version |
'3.12.13 (...) [SteelPython 0.1.0 on .NET 10.0, ...]' |
'3.12.13 (...) [MSC v.1944 64 bit (AMD64)]' |
platform.python_implementation() |
'CPython' |
'CPython' |
最後一行是有意為之:包通過這個問題來判斷 CPython 的 C API 和行為是否可用,而在 SteelPython 上它們是可用的。要檢測 SteelPython,請檢查 sys.implementation.name。
除錯與跟蹤
pdb、breakpoint()、sys.settrace、sys.monitoring、trace、cProfile 和 faulthandler 都可以使用。有兩處細粒度的細節不同:逐操作碼的跟蹤事件(frame.f_trace_opcodes)會被接受但從不報告;sys.monitoring 按每行而不是按每條位元組碼指令報告一次指令事件。偵錯程式和覆蓋率工具所使用的行級除錯與 CPython 相同。
互動式提示符
提示符和 input() 使用 SteelPython 自己的行編輯器,支援 GNU readline 的 Emacs 風格按鍵(歷史記錄、Ctrl+R 搜尋、剪下和貼上、Tab 補全、~/.inputrc 檔案)。它沒有 vi 模式。
效能
預計大多數純 Python 程式的執行時間會比在 CPython 3.12 上稍長:在典型的應用程式工作負載下,大約是其 1.5 到 2.5 倍。有些型別的程式碼比在 CPython 上執行得更快——尤其是緊湊的數值迴圈——而另一些則更慢,因此請對你關心的程式進行測量。
- 啟動很快:直譯器在不到一秒的時間內啟動,與 CPython 相當。
- 在編譯包內部完成的工作(對大陣列執行的 numpy 運算、pandas 分組聚合、在 GPU 上執行的 PyTorch 模型)以該包自身的速度執行,因為它們是同樣的編譯程式碼。
- 大量對編譯包的微小呼叫(對單個 numpy 標量執行數百萬次運算、用 mypyc 編譯的程式)每次呼叫的開銷都比在 CPython 上更大。請把更大塊的工作交給包來完成:進行向量化。
- 執行緒可以讓 CPU 密集型 Python 程式碼用上你所有的核心,這是 CPython 3.12 做不到的。一個在單核上較慢的程式,在八核上可能會快得多。
- 一段程式碼的首次執行比同一程序中後續的執行慢,因為 .NET 需要編譯它;長時間執行的程式和伺服器在預熱後會達到全速。