Differences from CPython

SteelPython follows CPython 3.12, the reference implementation of Python. The official documentation applies to SteelPython unless this page says otherwise; most programs, and most packages from PyPI, run unchanged.

What is not there

Missing Instead
tkinter, turtle, IDLE (idlelib) There is no Tk. Use Misp as the editor; for windows of your own, use a .NET UI library through clr, or a web UI such as streamlit.
Subinterpreters (_xxsubinterpreters, _xxinterpchannels) Threads (which run in parallel, see below) or multiprocessing.
The test package (CPython's own test suite) Not shipped.
._pth files Set PYTHONPATH or SPYPATH, or use a virtual environment.
A single bytes object larger than 2 GiB Larger files, memory maps and memoryviews work; only one bytes object (for example mmap_object[:] of a 3 GB file) cannot exceed 2 GiB. Work in slices.

The per-user site directory (pip install --user) is SteelPython's own: %APPDATA%\SteelPython\Python312\site-packages, not CPython's %APPDATA%\Python\Python312, so the packages one installs never confuse the other.

curses and os.fork() are missing too, as they are on CPython for Windows; multiprocessing starts its workers with spawn, as on CPython for Windows.

Threads run in parallel

SteelPython has no global interpreter lock (GIL). Python threads run on separate cores at the same time, so CPU-bound work split across a ThreadPoolExecutor does get faster, the way it does on the free-threaded builds of CPython 3.13 and later.

The guarantees are the same as there: a single operation on a built-in object (list.append, dict[key] = value, next() of a shared iterator, ...) is atomic, and no data race can crash the interpreter or corrupt its state. Compound operations - counter += 1 on a shared variable, check-then-act sequences - need a threading.Lock, exactly as they always have in Python. Code that is correct on CPython is correct on SteelPython; code that silently relied on the GIL to serialise a compound operation may now show the race.

When objects are freed

CPython frees an object the moment its last reference disappears. SteelPython's objects are freed by the .NET garbage collector, which runs from time to time. That makes a difference only for code that observes the moment of freeing:

  • __del__ methods, weakref callbacks and weakref.finalize run when the collector finds the object unreachable, or at the next gc.collect(), rather than at the last del. They do run, once, on a Python thread.
  • Files and other resources you did not close are closed when they are collected (with the usual ResourceWarning). In the common cases - a file opened into a local variable or used as a temporary, a generator or coroutine held only by a local - SteelPython releases the object as soon as that reference goes, as CPython would. Everywhere else, close resources explicitly, which is what Python's own documentation recommends anyway: use with open(...) as f:.
  • sys.getrefcount() does not report a real count for ordinary objects, and gc.collect() returns the number of finalizers it ran rather than CPython's count of unreachable objects. Code that uses reference counts to make decisions (rare outside test suites) behaves as if every object were shared.

Compiled files and bytecode

SteelPython caches compiled modules in __pycache__ as .pyc files named module.steelpython-312.pyc. They sit beside CPython's module.cpython-312.pyc without conflict, but neither implementation can read the other's.

SteelPython does not run CPython bytecode. dis shows the bytecode CPython would produce for the same source, so tools that inspect code objects work, and code objects can be copied and rebuilt (code.replace(), types.CodeType(...)) from functions SteelPython compiled. A code object assembled from bytecode written by hand, or produced by CPython, raises SystemError when called. marshal of code objects uses SteelPython's own format.

Identifying the interpreter

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'

The last row is deliberate: packages ask that question to decide whether CPython's C API and behaviour are available, and on SteelPython they are. To detect SteelPython, test sys.implementation.name.

Debugging and tracing

pdb, breakpoint(), sys.settrace, sys.monitoring, trace, cProfile and faulthandler work. Two fine-grained details differ: per-opcode trace events (frame.f_trace_opcodes) are accepted but never reported, and sys.monitoring reports instruction events once per line rather than once per bytecode instruction. Line-level debugging, which is what debuggers and coverage tools use, is the same as CPython's.

The interactive prompt

The prompt and input() use SteelPython's own line editor, with the Emacs-style keys of GNU readline (history, Ctrl+R search, kill and yank, Tab completion, ~/.inputrc files). It has no vi mode.

Performance

Expect most pure-Python programs to take somewhat longer than on CPython 3.12: on typical application workloads, roughly 1.5 to 2.5 times as long. Some kinds of code run faster than on CPython - tight numeric loops in particular - and others slower, so measure the program you care about.

  • Startup is fast: the interpreter starts in a fraction of a second, comparable to CPython.
  • Work done inside compiled packages (a numpy operation on a large array, a pandas group-by, a PyTorch model on the GPU) runs at the package's own speed, because it is the same compiled code.
  • Many tiny calls into compiled packages (millions of operations on single numpy scalars, programs compiled with mypyc) cost more per call than on CPython. Hand packages larger pieces of work: vectorise.
  • Threads use all your cores for CPU-bound Python code, which CPython 3.12 cannot do. A program that is slower on one core can be much faster on eight.
  • The first run of a piece of code is slower than later runs in the same process, as .NET compiles it; long-running programs and servers reach full speed after warming up.