Skip to main content

Modules/_abc.c

cpython 3.14 @ ab2d84fe1023/Modules/_abc.c

C accelerator that backs the abc standard-library module. The pure-Python fallback lives in Lib/_py_abc.py; this file shadows it when the interpreter is built with C extensions. The primary goal is speed: ABC subclass checks are on the hot path for isinstance and issubclass calls throughout the standard library, so even a modest speedup has broad impact.

Map

SymbolKindPurpose
_ABCDataC structPer-class bookkeeping: caches + version counter
_abc_initfunctionAttaches an _ABCData block to a new ABC class
_abc_registerfunctionAdds a class to _abc_cache (the registered-subclass WeakSet)
_abc_subclasscheckfunctionMain subclass check; consults caches then __mro__ and __subclasshook__
_abc_instancecheckfunctionDelegates to _abc_subclasscheck(type(obj), ...)
get_cache_tokenfunctionReturns the global ABC invalidation counter
_reset_cachesfunctionTest helper; clears both WeakSets and bumps the version

Per-class state: _ABCData

typedef struct {
PyObject *_abc_registry; /* unused legacy slot */
PyObject *_abc_cache; /* WeakSet of positively registered types */
PyObject *_abc_negative_cache; /* WeakSet of known non-subclasses */
unsigned long long _abc_negative_cache_version;
} _ABCData;

The _abc_negative_cache_version field is compared against the module-level _abc_invalidation_counter. When they differ the negative cache is stale and must be ignored.

Reading

Cache lookup in _abc_subclasscheck

The fast path checks the positive cache first, then the negative cache, before touching __mro__ or calling Python-level hooks.

/* Positive cache hit - return True immediately */
r = _PySet_Contains((PyObject *)data->_abc_cache, subclass);
if (r > 0) {
Py_RETURN_TRUE;
}

/* Negative cache is only valid when the version matches */
if (data->_abc_negative_cache_version == _abc_invalidation_counter) {
r = _PySet_Contains((PyObject *)data->_abc_negative_cache, subclass);
if (r > 0) {
Py_RETURN_FALSE;
}
}

A cache miss falls through to the full __mro__ walk and __subclasshook__ call. If __subclasshook__ returns NotImplemented the check continues with the MRO scan.

__subclasshook__ dispatch

ok = PyObject_CallMethodOneArg((PyObject *)cls,
&_Py_ID(__subclasshook__), subclass);
if (ok != NULL) {
if (ok == Py_NotImplemented) {
Py_DECREF(ok);
/* fall through to MRO walk */
} else {
int result = PyObject_IsTrue(ok);
Py_DECREF(ok);
if (result > 0) {
/* positive: store in cache and return True */
}
/* negative: store in negative cache, return False */
}
}

The function populates whichever cache is appropriate so the next call hits the fast path.

get_cache_token

static PyObject *
_abc__get_dump(PyObject *module, PyObject *self)
{
/* Returns the global counter as a Python int.
User code can compare two calls to detect any ABC registration
event occurring in between. */
_abcmodule_state *state = _abcmodule_get_state(module);
return PyLong_FromUnsignedLongLong(state->abc_invalidation_counter);
}

Every call to ABCMeta.register() increments the counter, which invaldiates all negative caches across all ABC classes simultaneously. This is intentionally coarse-grained: a single counter avoids per-class locking.

gopy mirror

Not yet ported to gopy. The relevant hook points are objects/type.go (for __subclasshook__ dispatch) and a future module/abc/ package.

CPython 3.14 changes

  • The legacy _abc_registry slot (present for pickle compatibility since 3.4) was removed from the public C API surface; the field still exists in the struct for binary compatibility but is no longer accessible from Python.
  • Module state is now stored via the per-module state API (Py_mod_multiple_interpreters), making the invalidation counter per-interpreter rather than process-global.