Skip to main content

Lib/abc.py (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/abc.py

This annotation covers virtual subclass registration and the __subclasshook__ mechanism. See lib_abc2_detail for ABCMeta.__new__, @abstractmethod, and the _abc_data C struct, and lib_abc_detail for ABC, abstractmethod, and abstractproperty.

Map

LinesSymbolRole
1-80ABCMeta.registerRegister a class as a virtual subclass
81-180ABCMeta.__subclasscheck__issubclass(X, ABC) dispatch
181-280__subclasshook__Structural check hook for custom logic
281-400Cache invalidationClear _abc_negative_cache when register is called

Reading

ABCMeta.register

# CPython: Lib/abc.py:88 ABCMeta.register
def register(cls, subclass):
"""Register a virtual subclass of an ABC."""
if not isinstance(subclass, type):
raise TypeError("Can only register classes")
if issubclass(subclass, cls):
return subclass # already a subclass
# Subtle: test for cycles *after* testing for "already a subclass".
if issubclass(cls, subclass):
raise RuntimeError("Refusing to create an inheritance cycle")
cls._abc_registry.add(subclass)
ABCMeta._abc_invalidation_counter += 1
return subclass

Iterable.register(MyClass) makes issubclass(MyClass, Iterable) return True without MyClass inheriting from Iterable. The global _abc_invalidation_counter increments to invalidate all ABC caches.

ABCMeta.__subclasscheck__

# CPython: Lib/abc.py:100 ABCMeta.__subclasscheck__
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
# Check the subclass hook first (for structural checks)
ok = cls.__subclasshook__(subclass)
if ok is not NotImplemented:
return ok
# Check the negative cache
if subclass in cls._abc_negative_cache:
return False
# Check the positive cache / registered set
if subclass in cls._abc_cache:
return True
# Walk the MRO of subclass for a real match
for scls in subclass.__mro__:
if scls in cls._abc_registry:
cls._abc_cache.add(subclass)
return True
# Recurse into registered subclasses of cls
for rcls in cls._abc_registry:
if issubclass(subclass, rcls):
cls._abc_cache.add(subclass)
return True
cls._abc_negative_cache.add(subclass)
return False

The two-level cache (positive set _abc_cache and negative set _abc_negative_cache) makes repeated isinstance / issubclass calls O(1) after the first. Both caches are WeakSet so they don't prevent GC.

__subclasshook__

# CPython: Lib/abc.py:180 __subclasshook__ in Sized
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented

__subclasshook__ is a classmethod that returns True, False, or NotImplemented. NotImplemented falls through to the registry / MRO check. _check_methods(C, '__len__') checks whether any class in C.__mro__ defines __len__ (not just inherits it from object).

Cache invalidation

# CPython: Lib/abc.py:96 _abc_invalidation_counter
# In _abc.c (C implementation), the cache is stored as a set on each ABCMeta.
# The global invalidation counter is checked on each __subclasscheck__ call.
# If the counter has changed since the cache was built, the cache is cleared.
# This avoids a global lock for cache invalidation.

The invalidation counter is a global integer. Each ABCMeta stores the counter value at cache-build time. A mismatch causes cache eviction. The C implementation (Modules/_abc.c) uses the same mechanism with C-level sets for speed.

gopy notes

ABCMeta.register is objects.ABCRegister in objects/abc.go. The registry is a map[objects.Type]struct{}. __subclasscheck__ is objects.ABCSubclassCheck. The cache uses sync.Map for concurrent access. __subclasshook__ is called via objects.CallClassmethod.