简体中文
English 简体中文 繁體中文
Methodox 首页

与 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 需要编译它;长时间运行的程序和服务器在预热后会达到全速。