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) | Symbol | Kind | Purpose |
|---|---|---|---|
| 1-80 | _ABCData | struct | Per-class cache: four WeakSet fields plus _abc_negative_cache_version |
| 81-130 | _abc_data_new, _abc_data_dealloc | functions | Allocation, GC traverse, and clear for the sidecar object |
| 131-200 | module state struct | struct | Holds the global abc_invalidation_counter |
| 201-260 | _abc_init | function | Creates and attaches a fresh _ABCData to a new ABCMeta class |
| 261-330 | _abc_register | function | Adds a class to _abc_registry, bumps the invalidation counter |
| 331-430 | _abc_instancecheck | function | Cache lookup (fast path), subclass hook, registry check (slow path) |
| 431-520 | _abc_subclasscheck | function | MRO walk with negative-cache short-circuit |
| 521-560 | _abc_caches_clear | function | Clears all four WeakSet fields on demand |
| 561-600 | _abc_get_cache_token, module init | functions | Returns 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:
-
Fast path. If
impl->_abc_negative_cache_versionequals the module counter, the caches are valid. Check_abc_cachefortype(instance)viaPySet_Contains. A hit returnsTrueimmediately without calling any Python code. -
Subclass hook. Call
cls.__subclasshook__(type(instance)). If it returns anything other thanNotImplemented, use that as the answer and update the appropriate cache. -
Registry check. Walk
type(instance).__mro__and test each entry against_abc_registry. A hit addstype(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:
_ABCDatamaps to anabcDatastruct stored as a field on theABCMetatype object viaobjects.Type.Extra. It holds four*objects.Setvalues (using weak-reference semantics) plus auint64version field.- The global
abc_invalidation_counterbecomes a package-leveluint64inmodule/abc/module.go, incremented withatomic.AddUint64so that concurrentregister()calls under a free-threaded build are safe. _abc_instancecheckcache lookups useobjects.SetContainson the positive set, checking the version field first before any set operation._abc_caches_clearcallsobjects.SetClearon both cache sets and increments the counter, matching CPython's conservative invalidation policy.- WeakSet semantics are approximated with
map[uintptr]*weakRefkeyed onobjects.TypeID(t), with dead entries pruned during type deallocation.