Skip to main content

Modules/_abc.c

Modules/_abc.c is the C accelerator for abc.ABCMeta. The pure-Python fallback lives in Lib/_py_abc.py and is behaviorally identical. The C layer exists because isinstance and issubclass checks against ABCs appear on hot paths in much of the standard library, and the cache lookups benefit significantly from avoiding Python frame overhead.

The file is self-contained: it defines one internal struct (_ABCData), a per-module state struct, and roughly a dozen exported functions that Lib/abc.py calls via _abc._abc_init, _abc._abc_register, and so on.

cpython 3.14 @ ab2d84fe1023/Modules/_abc.c

Map

Lines (approx)SymbolKindPurpose
1-80_ABCDatastructPer-class cache: four WeakSet fields plus _abc_negative_cache_version
81-130_abc_data_new, _abc_data_deallocfunctionsAllocation, GC traverse, and clear for the sidecar object
131-200module state structstructHolds the global abc_invalidation_counter
201-260_abc_initfunctionCreates and attaches a fresh _ABCData to a new ABCMeta class
261-330_abc_registerfunctionAdds a class to _abc_registry, bumps the invalidation counter
331-430_abc_instancecheckfunctionCache lookup (fast path), subclass hook, registry check (slow path)
431-520_abc_subclasscheckfunctionMRO walk with negative-cache short-circuit
521-560_abc_caches_clearfunctionClears all four WeakSet fields on demand
561-600_abc_get_cache_token, module initfunctionsReturns current counter as Python int; registers types and functions

Reading

_ABCData: the four-WeakSet layout

Each ABCMeta class carries an _ABCData sidecar object stored as its __abc_impl__ attribute. The struct holds four WeakSet references and a version field:

// CPython: Modules/_abc.c:43 _ABCData
typedef struct {
PyObject_HEAD
PyObject *_abc_registry; /* WeakSet of explicitly registered classes */
PyObject *_abc_cache; /* WeakSet: positive isinstance results */
PyObject *_abc_negative_cache; /* WeakSet: negative isinstance results */
PyObject *_abc_subclasses; /* WeakSet: direct ABC subclasses */
unsigned long long _abc_negative_cache_version;
} _ABCData;

All four sets use weakref entries so that dynamically created types do not keep each other alive through the cache. Dead references are silently ignored by PySet_Contains because the WeakSet implementation filters them on access.

_abc_negative_cache_version is a per-instance shadow of the module-level abc_invalidation_counter. When the two values differ the negative cache is stale and must be cleared before the next check. This design lets register() on any class invalidate all negative caches across every ABCMeta instance with a single counter increment, without touching any individual instance.

_abc_init and _abc_register

_abc_init is called from ABCMeta.__new__. It allocates a fresh _ABCData, initializes all four sets to empty WeakSet objects, sets _abc_negative_cache_version to the current counter value, and attaches the object as cls.__abc_impl__.

// CPython: Modules/_abc.c:201 _abc_init
static PyObject *
_abc_init(PyObject *module, PyObject *cls)
{
_ABCData *impl = PyObject_GC_New(_ABCData, &_ABCData_Type);
if (impl == NULL) return NULL;

impl->_abc_registry = PyObject_CallNoArgs((PyObject *)&_PyWeakrefSet_Type);
impl->_abc_cache = PyObject_CallNoArgs((PyObject *)&_PyWeakrefSet_Type);
impl->_abc_negative_cache = PyObject_CallNoArgs((PyObject *)&_PyWeakrefSet_Type);
impl->_abc_subclasses = PyObject_CallNoArgs((PyObject *)&_PyWeakrefSet_Type);
impl->_abc_negative_cache_version = get_state(module)->abc_invalidation_counter;

if (PyObject_SetAttr(cls, &_Py_ID(__abc_impl__), (PyObject *)impl) < 0) {
Py_DECREF(impl); return NULL;
}
return Py_None;
}

_abc_register adds the new virtual subclass to _abc_registry and then increments the module-level counter. Because every living _ABCData instance caches the counter at the time its negative cache was last valid, a single increment makes all negative caches stale at once.

// CPython: Modules/_abc.c:268 _abc_register
static PyObject *
_abc_register(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
/* ... extract cls and subclass from args ... */
if (PyObject_CallMethodOneArg(impl->_abc_registry,
&_Py_ID(add), subclass) == NULL)
return NULL;
get_state(module)->abc_invalidation_counter++;
Py_INCREF(subclass);
return subclass;
}

_abc_instancecheck: cache lookup, subclass hook, registry check

_abc_instancecheck(cls, instance) is the implementation of ABCMeta.__instancecheck__. It proceeds in three stages:

  1. Fast path. If impl->_abc_negative_cache_version equals the module counter, the caches are valid. Check _abc_cache for type(instance) via PySet_Contains. A hit returns True immediately without calling any Python code.

  2. Subclass hook. Call cls.__subclasshook__(type(instance)). If it returns anything other than NotImplemented, use that as the answer and update the appropriate cache.

  3. Registry check. Walk type(instance).__mro__ and test each entry against _abc_registry. A hit adds type(instance) to _abc_cache.

// CPython: Modules/_abc.c:331 _abc_instancecheck
static PyObject *
_abc_instancecheck(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *subtype = (PyObject *)Py_TYPE(instance);
_abcmodule_state *state = get_state(module);

/* fast path: check positive cache */
if (impl->_abc_negative_cache_version == state->abc_invalidation_counter) {
int r = PyObject_CallMethodOneArg(
impl->_abc_cache, &_Py_ID(__contains__), subtype);
if (r) Py_RETURN_TRUE;
}

/* subclass hook */
PyObject *ok = PyObject_CallMethodOneArg(
(PyObject *)cls, &_Py_ID(__subclasshook__), subtype);
if (ok != Py_NotImplemented)
return ok;

/* registry walk via _abc_subclasscheck */
return _abc_subclasscheck_impl(module, cls, subtype);
}

After a confirmed negative result, type(instance) is added to _abc_negative_cache and _abc_negative_cache_version is updated to match the current counter, so the next check can short-circuit at the fast path.

_abc_caches_clear and the version counter

_abc_caches_clear empties _abc_cache and _abc_negative_cache on demand. It does not touch _abc_registry or _abc_subclasses, which are not caches but authoritative membership sets.

// CPython: Modules/_abc.c:521 _abc_caches_clear
static PyObject *
_abc_caches_clear(PyObject *module, PyObject *cls)
{
_ABCData *impl = _get_impl(module, cls);
if (impl == NULL) return NULL;

if (PyObject_CallMethodNoArgs(impl->_abc_cache,
&_Py_ID(clear)) == NULL) return NULL;
if (PyObject_CallMethodNoArgs(impl->_abc_negative_cache,
&_Py_ID(clear)) == NULL) return NULL;

/* Reset the version so the next check skips the stale fast path */
get_state(module)->abc_invalidation_counter++;
impl->_abc_negative_cache_version =
get_state(module)->abc_invalidation_counter;
Py_RETURN_NONE;
}

The global counter increment here ensures that any other ABCMeta instance that had a valid negative cache will now see a version mismatch and rebuild before its next check. This is conservative but correct: an explicit cache clear on one class invalidates the negative results for all classes, which matches the documented contract of ABCMeta._abc_caches_clear.

gopy notes

Status: not yet ported. Planned package path: module/abc/.

Translation decisions for the Go port:

  • _ABCData maps to an abcData struct stored as a field on the ABCMeta type object via objects.Type.Extra. It holds four *objects.Set values (using weak-reference semantics) plus a uint64 version field.
  • The global abc_invalidation_counter becomes a package-level uint64 in module/abc/module.go, incremented with atomic.AddUint64 so that concurrent register() calls under a free-threaded build are safe.
  • _abc_instancecheck cache lookups use objects.SetContains on the positive set, checking the version field first before any set operation.
  • _abc_caches_clear calls objects.SetClear on both cache sets and increments the counter, matching CPython's conservative invalidation policy.
  • WeakSet semantics are approximated with map[uintptr]*weakRef keyed on objects.TypeID(t), with dead entries pruned during type deallocation.