Skip to main content

v0.12.2 - Stdlib import chain

Released May 12, 2026.

When you import re in Python, an enormous amount of machinery has to be in place for that one line to work. The re package itself is Python code. It imports _sre, which is a C extension. _sre calls into a regex engine that walks a bytecode the Python-level re._compiler produces. The compiler itself imports enum, functools, unicodedata, _thread. Each of those imports pulls in more modules. By the time the chain settles, your program has loaded forty files and crossed the Python / C boundary a dozen times.

For most of gopy's life, we faked our way through this chain. We shimmed re to a thin wrapper around Go's regexp. We shimmed functools to a handful of stubs. We shimmed _colorize, traceback, warnings, and a long tail of others because porting the real module would have meant porting half of CPython first.

v0.12.2 ends the shim era. Thirty stdlib modules now import the real CPython sources from Lib/, and those files resolve against 1:1 Go ports of their C accelerators. The regex engine in particular is a complete rewrite: gone is the RE2 shim, in is a real interpreter that walks the CPython opcode stream the way Modules/_sre/sre_lib.h does.

Phase 1 of the object protocol full port (spec 1704) lands here too. The Objects/object.c method and getset tables now live on objectType as real descriptors, and the metatype typeType inherits from objectType so attribute lookup on a class walks into object's descriptors the same way it does in CPython.

Highlights

Three pieces of work define this release.

Real regex engine

The Python-level re package now loads from stdlib/re/, byte identical to CPython 3.14. It imports _sre, which is our 1:1 Go port of Modules/_sre. Patterns are compiled by the Python-level re._compiler into the same opcode buffer CPython produces, and we walk that buffer with a real backtracking match loop ported from Modules/_sre/sre_lib.h.

The RE2 shim is gone. There's no fallback path. Every re.match, re.search, re.findall, re.sub, re.split rides the same engine CPython does.

import re

m = re.match(r'(\d+)-(\d+)', '12-34')
print(m.groups()) # ('12', '34')
print(m.span(1), m.span(2)) # (0, 2) (3, 5)

# Real \b word boundaries, real (?i) case folding, real
# (?P<name>...) named groups, real backreferences.
re.sub(r'\b(\w+)\s+\1\b', r'\1', 'the the cat')
# 'the cat'

Thirty modules cross the line

The headline ports are:

ModuleWhat it isSource
_sreRegex engineModules/_sre/*
_datetimedate, time, datetime, timedelta, timezoneModules/_datetimemodule.c
_hashlibMD5, SHA family, HMAC, PBKDF2Modules/_hashopenssl.c
_structpack, unpack, calcsizeModules/_struct.c
_randomMersenne TwisterModules/_randommodule.c
_socketTCP / UDP / Unix sockets, getaddrinfoModules/socketmodule.c
_ioBytesIO, StringIO, FileIO, buffered IO, TextIOWrapperModules/_io/*
_csvCSV reader / writerModules/_csv.c
_codecsCodec registryModules/_codecsmodule.c
_collectionsdeque, defaultdict, OrderedDict, CounterModules/_collectionsmodule.c
_itertoolsTwenty-plus iteratorsModules/itertoolsmodule.c
_functoolsreduce, partial, lru_cacheModules/_functoolsmodule.c
_operatorC-level operator functionsModules/_operator.c
_abcABCMeta C acceleratorModules/_abc.c
_weakrefref, proxy, CallableProxyTypeModules/_weakrefmodule.c
_threadget_ident, locks, start_new_threadModules/_threadmodule.c
_heapqBinary heapModules/_heapqmodule.c
_bisectBisection algorithmsModules/_bisectmodule.c
_statS_IF* constants and predicatesModules/_stat.c
_jsonJSON scanner and encoder fast pathModules/_json.c
_uuidRFC 4122 UUID generationModules/_uuidmodule.c
_posixsubprocessfork_execModules/_posixsubprocess.c
_warningsWarnings filter machineryModules/_warnings.c
_timeReal-time clock surfaceModules/timemodule.c
mathThe full math.__all__Modules/mathmodule.c

On the Python side, the corresponding .py files now resolve to vendored CPython sources rather than shims: re, datetime, hashlib, subprocess, threading, json, logging, warnings, traceback, unittest, abc, functools, contextlib, operator, reprlib, pprint, difflib, _colorize, email, urllib, http, base64, configparser, tarfile, zipfile, tokenize, linecache, textwrap, codeop, numbers, random, string, shutil, shlex, selectors, uuid, io, platform. Every one of these used to be a shim with a sticky note saying "vendor when the import chain is ready". The chain is ready.

Object protocol Phase 1

If you ask Python "what methods does object have?", CPython's answer is the list in Objects/object.c object_methods and Objects/object.c object_getsets. Up through v0.12.0, gopy's answer was "whatever I happened to wire up when I needed it for a specific test". That worked until enum needed object.__subclasshook__, and then abc needed object.__init_subclass__, and then a Python-level class needed __dir__ to walk the type chain.

Phase 1 of spec 1704 ports the full table 1:1. Every name in object_methods and object_getsets is now a real descriptor sitting on objectType. The metatype typeType inherits from objectType, so class-level attribute lookup walks the same path CPython does.

class C:
pass

# These all walk into the same descriptors CPython uses.
print(C.__dir__) # method
print(C.__subclasshook__) # classmethod
print(C.__init_subclass__) # classmethod
print(C.__class__ is type) # True
print(C().__format__('')) # ''

This work unblocks the enum import end to end and clears the last open piece of issue #544.

What's new

The full feature breakdown, grouped by where it landed.

Test e2e harness

This release closes the gap that v0.12.1 was supposed to close (but never tagged). The CPython Lib/test/test_*.py suite is now the gopy regression gate.

  • test/regrtest/. A 1:1 port of Lib/test/regrtest.py. It walks a MANIFEST.txt triage table, runs each ready entry through gopy, and reports pass / fail / skip per file. The manifest tracks all 434 CPython 3.14 test entry points; we ship the ones that pass and mark stdlib-dependent tests (json, pickle, networking, asyncio) as deferred until those modules land. The triage table lives at ~/notes/Spec/1700/1700_gopy_test_e2e.md.
  • test/cpython/. Vendored Lib/test/test_*.py files for shipped features, byte identical to upstream. Why byte identical? Because we want future CPython rebases to show zero diff noise. If a test changes between CPython 3.14 and 3.15, we see exactly that change rather than a tangle of our own edits. test/cpython/support/ carries Lib/test/support/ helpers unchanged for the same reason.
  • imp/pathfinder.go. FileFinder plus SourceFileLoader ported from Lib/importlib/_bootstrap_external.py. The path finder walks sys.path, resolves package __init__.py directories, compiles source on demand, and caches the resulting Code object on the loader. The cache is invalidated on file mtime change so editing a vendored .py and rerunning picks up the new version without a restart.
  • stdlibinit/. A port of Modules/config.c.in. Built-in modules used to register themselves in scattered init() functions across the tree. Now there's one table, generated the way CPython generates Modules/config.c, so adding a new built-in is one entry rather than a hunt through the codebase.

Regex engine

The big one. v0.12.0 shipped a regex shim that delegated to Go's regexp package (PCRE-style but RE2-backed). That worked for maybe 70% of real-world patterns and failed the rest because the shim couldn't honor backtracking, named groups, (?P=name) backreferences, lookaround assertions, or several escape classes that RE2 deliberately omits for performance.

This release replaces the shim with a real interpreter.

  • objects/sre/_constants.go. A 1:1 port of Lib/re/_constants.py. This is the opcode and flag table the rest of the engine keys off. We chose to port it (rather than reuse Lib/re/_constants.py at runtime) because the values are load-bearing in the Go code path; keeping them as Go constants lets the compiler check that we never reference a non-existent opcode.
  • objects/sre/_casefix.go. The 46-entry case-fold table the C engine consults at compile time when the pattern carries the (?i) flag. CPython generates this table from Unicode data; we copied the table verbatim and added a regen script so Unicode 16 updates land as a one-line bump.
  • objects/sre/engine.go. The real meat. This is Modules/_sre/sre_lib.h translated to Go. Every opcode in the CPython instruction set is here: LITERAL, LITERAL_IGNORE, IN, IN_IGNORE, ANY, ANY_ALL, REPEAT, MAX_UNTIL, MIN_UNTIL, MARK, JUMP, BRANCH, ASSERT, ASSERT_NOT, AT (the anchor family), GROUPREF, GROUPREF_IGNORE, GROUPREF_EXISTS, CHARSET. The match loop is a faithful Go translation including the stack-based backtracking state.
  • objects/sre/pattern.go. The Pattern surface: match, fullmatch, search, findall, finditer, sub, subn, split. compile runs the source through the Python-level re._parser and re._compiler to produce the same opcode stream CPython would, then stamps the bytecode onto the Pattern object so subsequent calls don't recompile. No more shim fallback.
  • objects/sre/match.go. The Match surface: group, groups, groupdict, start, end, span, expand, regs, lastgroup, lastindex. These are 1:1 ports of Modules/_sre/sre.c match_*.

The CI now refuses to merge a change that re-introduces the regexp import to any of the regex code paths. We learned the hard way during the v0.10 to v0.12 stretch that "small fallback, we'll remove it later" turns into a load-bearing fallback faster than anyone expects.

Why we ported Modules/_sre instead of keeping regexp

Three reasons.

The behavior mismatch was bigger than we'd hoped. re.search and regexp.FindIndex agree on the easy cases and disagree on the hard ones. Backreferences ((.+)\1), lookbehind assertions ((?<=foo)bar), and possessive quantifiers (a++) all fall out of RE2's design constraints. Patterns that mixed those features silently produced wrong matches against a Go regexp engine, and "silently wrong" is the worst kind of failure for a runtime trying to be a drop-in.

Performance was a wash. We assumed RE2 would crush a faithful port on pathological patterns and that the win would offset the fidelity cost. Benchmarks said otherwise. For the patterns real Python programs actually compile, the CPython backtracker is faster than RE2 plus the conversion layer we needed on top.

Compatibility with re._compiler matters. The Python-level compiler emits an opcode stream the C engine consumes. If we keep the C engine, we get the Python compiler for free, and any future Python-level features land without engine changes. If we keep a foreign engine like RE2, every Python-level change has to be mirrored across the conversion boundary. The accounting cost was not small.

C-extension ports

Each entry below is a 1:1 port of the named CPython source. Where the C version touches a C-only facility (OpenSSL, raw POSIX syscalls), we substituted the Go equivalent and kept the public-surface behavior identical.

  • _abc. Modules/_abc.c. The ABCMeta accelerator: _abc_init, _abc_register, _abc_instancecheck, _abc_subclasscheck, get_cache_token. The Python-level abc.py now loads from stdlib/abc.py and resolves its C-side hooks through this module.
  • _operator. Modules/_operator.c. Every name in operator.__all__ (add, sub, mul, itemgetter, attrgetter, methodcaller, the rich-compare helpers, the in-place arithmetic helpers) backed by the C-level fast path.
  • _weakref. Modules/_weakrefmodule.c. ref, proxy, CallableProxyType, getweakrefcount, getweakrefs. The underlying weakref machinery already lived in objects/weakref.go from v0.10.1; this release wires the C module surface on top so weakref.ref(obj, callback) walks through the same call path CPython uses.
  • _functools. Modules/_functoolsmodule.c. reduce, partial, partialmethod, lru_cache, cmp_to_key. The lru_cache decorator uses the same OrderedDict-backed LRU CPython does, with the same maxsize=128 default and the same cache statistics shape (hits, misses, maxsize, currsize).
  • _itertools. Modules/itertoolsmodule.c. Full 1:1 port covering every iterator: count, cycle, repeat, accumulate, chain, combinations, combinations_with_replacement, compress, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, starmap, takewhile, tee, zip_longest. Each iterator carries the upstream state layout so pickle.dumps on a partially-consumed combinations object produces bytes that unpickle to an equivalent iterator.
  • _collections. Modules/_collectionsmodule.c. deque, defaultdict, OrderedDict, Counter accelerators. deque in particular is performance critical; we ported the doubly linked list of fixed-size blocks the CPython version uses rather than wrapping container/list, because container/list allocates per node while the block layout amortizes.
  • _thread. Modules/_threadmodule.c. get_ident, lock type, start_new_thread, _count. Locks back onto Go sync.Mutex. start_new_thread launches a goroutine but cooperates with our GIL the way CPython's pthread-launched threads cooperate with theirs, so threading.Thread semantics hold.
  • _heapq. Modules/_heapqmodule.c. heappush, heappop, heappushpop, heapify, heapreplace, nlargest, nsmallest, plus the _heapify_max family used by heapq.merge.
  • _bisect. Modules/_bisectmodule.c. bisect_left, bisect_right, insort_left, insort_right. We deliberately did not optimize past a faithful port; the CPython implementation already takes O(log n) compares and the call overhead from Python dominates.
  • _stat. Modules/_stat.c. The S_IF* family of file-mode constants plus the S_IS* predicate helpers posix.stat_result leans on. Constants are shared with os so a single change propagates.
  • _struct. Modules/_struct.c. pack, unpack, pack_into, unpack_from, iter_unpack, calcsize. Format strings honor every byte-order prefix (<, >, =, !, @), native and standard sizes for b, B, h, H, i, I, l, L, q, Q, n, N, e, f, d, s, p, ?, x, and the padding rules CPython documents.
  • _random. Modules/_randommodule.c. The Mersenne Twister PRNG. getrandbits, random, seed, setstate, getstate. We kept the upstream state layout (624-element uint32 vector plus index) so random.getstate() produces tuples that round-trip through random.setstate() identically to CPython.
  • _hashlib. Modules/_hashopenssl.c. MD5, SHA1, SHA224, SHA256, SHA384, SHA512, BLAKE2b, BLAKE2s, HMAC, PBKDF2. Where the C version goes through OpenSSL, we go through Go's crypto/* packages. Output bytes are identical; performance is comparable on amd64 and slightly better on arm64.
  • _posixsubprocess. Modules/_posixsubprocess.c. The fork_exec entry point subprocess.Popen calls. We dispatch through os/exec.Cmd and honor the file-descriptor remapping CPython does so file descriptors inherit cleanly across the fork.
  • _socket. Modules/socketmodule.c. The full BSD-style socket interface plus getaddrinfo, gethostname, gethostbyname, getservbyname. The POSIX implementation uses syscall plus net; a Windows stub publishes the same surface so cross-platform builds compile.
  • _io. Modules/_io/*. The full hierarchy: RawIOBase -> BufferedIOBase -> TextIOBase. BytesIO, StringIO, FileIO, BufferedReader, BufferedWriter, BufferedRandom, TextIOWrapper. _io.open honors mode, buffering, encoding, newline, and errors through the layer stack. The codec layer hooks into _codecs for encoding / decoding.
  • _codecs. Modules/_codecsmodule.c. The codec registry, lookup, encode, decode. Backs str.encode, bytes.decode, and the io.TextIOWrapper encoding pipeline.
  • _csv. Modules/_csv.c. reader, writer, dialect registration, the quoting and lineterminator state machine.
  • _json. Modules/_json.c. scanstring, encode_basestring, encode_basestring_ascii. We ported the C scanner verbatim because the upstream json package conditionally uses these accelerators when available; if we publish the symbols, Python-level json.dumps and json.loads automatically take the fast path.
  • _uuid. Modules/_uuidmodule.c. RFC 4122 UUID generation. generate_time_safe and the random variants are backed by crypto/rand.
  • _datetime. Modules/_datetimemodule.c. date, time, datetime, timedelta, timezone. Arithmetic, formatting, parsing, and timezone handling all match CPython 3.14. We kept the upstream MINYEAR=1 / MAXYEAR=9999 bounds and the same representation choices (a datetime is a tuple of seven small ints plus an optional tzinfo, not a Go time.Time) so datetime.utcnow().toordinal() returns identical values to CPython.
  • _warnings. Modules/_warnings.c. warn, warn_explicit, _filters_mutated, the filter list, and the per-thread warnings state. Cooperates with stdlib/warnings.py for the Python-level surface.
  • _time. Modules/timemodule.c. The full time module: time, monotonic, perf_counter, process_time, sleep, gmtime, localtime, mktime, strftime, strptime, tzname. monotonic uses Go's time.Now() minus a process origin; perf_counter uses the high-resolution counter through runtime.nanotime.
  • math. Modules/mathmodule.c. Every name in math.__all__. pow, sqrt, log, log2, log10, exp, expm1, factorial, gcd, lcm, isclose, isnan, isinf, isfinite, comb, perm, dist, fsum, prod, remainder, nextafter, ulp, the trig family (sin, cos, tan, asin, acos, atan, atan2), and the hyperbolic family (sinh, cosh, tanh, asinh, acosh, atanh).

Vendored Python stdlib

stdlib/ carries byte-identical copies of the CPython 3.14 sources. The manifest at stdlib/MANIFEST.txt enumerates every vendored file so future CPython rebases stay scriptable: a single sync script reads the manifest, fetches the upstream files at the pinned commit, and reports diff vs the vendor tree.

Newly vendored in this release: traceback.py, copy.py, gettext.py, the logging/ package, the unittest/ package, _collections_abc.py, _weakrefset.py, _py_abc.py, abc.py, reprlib.py, operator.py, warnings.py, pprint.py, difflib.py, functools.py, contextlib.py, the re/ package (__init__.py, _compiler.py, _parser.py), datetime.py, numbers.py, hashlib.py, struct.py, random.py, threading.py, subprocess.py, selectors.py, shutil.py, shlex.py, uuid.py, the json/ package, string.py, linecache.py, tokenize.py, textwrap.py, codeop.py, io.py, platform.py, zipfile/, tarfile.py, base64.py, configparser.py, the email/ package, the urllib/ package, the http/ package, _colorize.py.

Each landed alongside a shim deletion. The import machinery now resolves the real file rather than a wrapper.

Object protocol Phase 1

Spec 1704 (private design doc) calls for porting every function in Objects/object.c, Objects/typeobject.c, Objects/classobject.c, Objects/funcobject.c, and the name opcodes in Python/ceval.c, in full. The deliverable is "every function in the C file has a Go counterpart with a citation". Once this spec lands we never come back to these files looking for a missing slot.

Phase 1 is the Objects/object.c block: object_methods and object_getsets.

// objects/object_proto.go
func init() {
SetTypeDescr(objectType, "__init__", ...)
SetTypeDescr(objectType, "__new__", ...)
SetTypeDescr(objectType, "__repr__", ...)
SetTypeDescr(objectType, "__str__", ...)
SetTypeDescr(objectType, "__hash__", ...)
SetTypeDescr(objectType, "__format__", ...)
SetTypeDescr(objectType, "__sizeof__", ...)
SetTypeDescr(objectType, "__dir__", ...)
SetTypeDescr(objectType, "__reduce__", ...)
SetTypeDescr(objectType, "__reduce_ex__", ...)
SetTypeDescr(objectType, "__getstate__", ...)
SetTypeDescr(objectType, "__subclasshook__", asClassMethod(...))
SetTypeDescr(objectType, "__init_subclass__", asClassMethod(...))
SetTypeDescr(objectType, "__class__", NewGetSet(...))
SetTypeDescr(objectType, "__dict__", NewGetSet(...))
// ...plus the six richcompare slots.
}

Two consequences fall out of getting this right.

typeType now inherits from objectType in the bases tuple, the same way PyType_Type inherits from PyBaseObject_Type in CPython. Before this change, attribute lookup on a class skipped right past object's descriptors because the MRO didn't walk through object. Now cls.__dir__, cls.__subclasshook__, and cls.__init_subclass__ all resolve the way they should.

NewUserType propagates __module__ from the building frame into the new class. __build_class__ no longer pre-stamps __name__ and __qualname__ into the class namespace either, because that pre-stamp was hiding the descriptor and producing a plain string where we wanted a getset.

Exception machinery

This was a quiet ship but a big one. Try / except / finally / with / exception groups all compile and execute through real runtime machinery for the first time.

  • vm/exc_unwind.go. The exception unwinding loop. When code raises, the unwinder walks the per-code-object exception table, finds the matching handler frame, runs PUSH_EXC_INFO / POP_EXCEPT pairing, and chains __context__ through the per-thread handled-exception slot. This is the runtime side of what compile/codegen.go try_except emits.
  • vm/generator.go. Generators save and restore the thread's exception state on __next__, send, throw, and close. Without this, an exception raised inside a generator body leaked into the calling frame's __context__ after the generator yielded.
  • errors/baseexc_getset.go. BaseException gets real getset descriptors for args, __traceback__, __context__, __cause__, __suppress_context__. Before this release, these were ad-hoc fields read directly off the Go struct, which meant e.__traceback__ returned the right value but setattr(e, '__traceback__', tb) silently no-op'd.
  • errors/raise.go. tp_call on every built-in exception type now goes through errors.Raise. RAISE_VARARGS is one arm rather than per-type special cases, which means raise OSError(errno.ENOENT, "msg") and raise OSError(errno.ENOENT, "msg", "filename") both produce the right exception with the right args shape without an arm-per-exception explosion.

Compile and VM polish

A backlog of compiler and VM fixes shipped alongside the bigger ports. None of these are headline features but several were load-bearing for the stdlib vendoring.

  • Closures, decorators, and *args calls route through one unified vectorcall dispatcher in vm/eval_call.go. Before this, three separate code paths were maintaining their own argument-shuffling logic.
  • Chained comparisons (a < b < c) evaluate left to right with short-circuit semantics in compile/compare.go. Each intermediate result lands on the stack as a boolean rather than the v0.11 wrong-side leaks where the chained value occasionally surfaced where the boolean should have been.
  • EXTENDED_ARG accumulation is correct across consecutive prefixes in compile/assemble.go. The old code dropped one prefix in three when two EXTENDED_ARG instructions appeared in a row, which truncated very long jumps in big functions.
  • Closure tuples for non-inlined comprehensions in compile/codegen.go. List, dict, and set comprehensions that reference outer-scope cells now get their closure tuple emitted correctly.
  • LOAD_DEREF names the missing cell in the raised error.
  • END_FOR is a no-op so the trailing POP_TOP pops the iter the way CPython 3.13+ expects.
  • MAKE_CELL runs for every cell variable, not just the ones whose name appeared in co_cellvars. The omission produced subtle "free variable referenced before assignment" errors in deeply nested closures.

Builtins surface

  • The BaseException family is exposed as Python-level names so except OSError: works against user code. Up through v0.12.0 these were registered on the type but not in the builtins namespace; OSError in an except clause raised NameError.
  • Type construction routes through tp_new consistently. int(), float(), str(), bytes(), list(), tuple(), dict(), set(), frozenset() all dispatch through one call slot. This matters because user subclasses (class L(list): pass) inherit the constructor through the same descriptor protocol the built-ins use.
  • sys.path is exposed so unittest.loader can discover test files. sys.argv and the config-derived attributes are stamped at module build time. sys.exc_info() reads the handled-exception slot the unwinder maintains.

Why this release took the shape it did

You'll notice this release pulls in a lot of work that was nominally v0.12.1. That's because v0.12.1 was an attempt to ship "just the test infrastructure" without the modules the tests depend on. We got far enough to wire the test runner and realize the next ten tests we wanted to land all needed re, datetime, or _io to work. So we pulled the import chain along with them.

The thirty-module batch is intentionally one release rather than thirty. Each module by itself is a small port, but the wiring between them (path finder, stdlibinit, codec layer, codec registry, generic getset machinery, BaseException getsets, the exception unwinder) is shared. Shipping them as a batch made the diff legible: one PR adds the wiring once, then each module ports against the same wiring. Shipping them one-by-one would have meant rebasing the wiring against itself thirty times.

The decision to vendor CPython sources rather than reimplement the Python-level files in Go-flavored Python deserves a callout. The rule is simple: if the file ends in .py upstream, we vendor it byte for byte. If it ends in .c, we port it 1:1 with a citation. This is annoying to do (CPython's coding style is its own thing) but it pays dividends: the diff against upstream is always small, and a fix in CPython 3.14.1 lands as a one-line manifest bump on our side rather than a multi-day re-implementation sweep.

Compatibility

A few user-visible changes are worth flagging.

  • Regex behavior changes. Patterns that previously worked against the RE2 backed shim and silently produced wrong matches now produce CPython-faithful matches. In particular, (.+)\1, lookbehind assertions, and possessive quantifiers all behave like CPython rather than like RE2. If you had code that compensated for the shim's quirks, that compensation can go.
  • object.__dir__ now walks the type chain the same way CPython does. Code that relied on the v0.12.0 short-list behavior may see additional entries in dir(obj).
  • BaseException attribute writes now actually take effect. If you had code like e.__traceback__ = tb that appeared to work but was silently dropped, it now does what you wrote.
  • sys.exc_info() returns the currently-handled exception per CPython semantics. If nothing is being handled, it returns (None, None, None). Old code that called it outside an except block to inspect a stale state will see the empty tuple now.

What's next

The remaining phases of spec 1704 land in v0.12.3.

  • Phase 2. Objects/funcobject.c PyClassMethod_Type block. Full port of every cm_* function: cm_init, cm_descr_get, cm_repr, cm_traverse, the cm_memberlist for __func__ and __wrapped__, the cm_getsetlist for __isabstractmethod__ / __dict__ / __annotations__ / __annotate__. __set_name__ forwarding.
  • Phase 3. Objects/funcobject.c PyStaticMethod_Type block. Mirror of Phase 2 plus sm_call so staticmethod(f)(x) works without .func indirection.
  • Phase 4. Objects/funcobject.c PyFunction_Type block. Full audit of func_* functions for the getset table.
  • Phase 5. Objects/classobject.c PyMethod_Type. Full bound-method port including method_richcompare, method_hash, method_repr, the full getset table.
  • Phase 6. Objects/typeobject.c type_new pipeline. Every function in type_new_* ported with the slots they wire.
  • Phase 7. Objects/typeobject.c inherit_slots. Audit every slot edge so subclasses inherit slots through the same propagation rules CPython uses.
  • Phase 8. Python/ceval.c STORE_NAME / LOAD_NAME / DELETE_NAME. The fast-path-vs-protocol split CPython does for dict subclasses as the class namespace.

Outside spec 1704, the v0.12.3 cut also pulls in:

  • The os, posixpath, ntpath full ports (issue #518).
  • argparse polish past the v0.12.2 vendor.
  • The io split refinements past the v0.12.2 layer stack.
  • The fnmatch option-A delegation to re.compile(translate) (issue #542).
  • enum.FlagBoundary import end to end (issue #544).

Networking, multiprocessing, asyncio, sqlite3, ctypes, tk / curses, GUI tests, gdb / dtrace stay out of scope. Those land when their respective porting specs do.

Acknowledgments

This release closes work tracked across roughly forty internal specs. The public-facing pointers are:

  • Spec 1700 (test e2e drop) and the manifest at ~/notes/Spec/1700/1700_gopy_test_e2e.md.
  • Spec 1703 (regex full port). 8 phases, all shipped here.
  • Spec 1704 (object protocol full port). Phase 1 shipped here.
  • Issue #544 (enum import). Object protocol Phase 1 closes the last open piece.

The pull request that shipped this release is #22. The commit log covering everything since v0.12.0 is at compare v0.12.0..v0.12.2.