python/cpython · error · RuntimeError
Refusing to create an inheritance cycle
Error message
Refusing to create an inheritance cycle
What it means
Raised by ABCMeta.register when the requested virtual-subclass relationship would create an inheritance cycle — specifically when the ABC itself is already a (real or virtual) subclass of the class you are trying to register. Because virtual subclass checks walk cls.__subclasses__()-style registries, a cycle would break __subclasshook__ resolution, so register() refuses with RuntimeError. Note X.register(X) is explicitly a no-op, and real subclasses are accepted silently.
Source
Thrown at Lib/_py_abc.py:67
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls
def register(cls, subclass):
"""Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
"""
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";
# this means we allow X.register(X) and interpret it as a no-op.
if issubclass(cls, subclass):
# This would create a cycle, which is bad for the algorithm below
raise RuntimeError("Refusing to create an inheritance cycle")
cls._abc_registry.add(subclass)
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
return subclass
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
for name in cls.__dict__:
if name.startswith("_abc_"):
value = getattr(cls, name)
if isinstance(value, WeakSet):
value = set(value)
print(f"{name}: {value!r}", file=file)
def _abc_registry_clear(cls):
"""Clear the registry (for debugging or testing)."""
cls._abc_registry.clear()View on GitHub (pinned to bc6749cc3b)
Solutions
- Reverse the direction: register the derived class with the base ABC (BaseABC.register(Derived)), not the reverse
- If the class is already a real subclass, drop the register() call entirely — it is redundant
- Restructure so the 'provider' side is always the ABC and the 'implementer' side is always the argument
- Guard with issubclass checks before registering (see defense below)
Example fix
# before class Serializer(ABC): ... class JSONSerializer(Serializer): ... JSONSerializer.register(Serializer) # RuntimeError: cycle # after # JSONSerializer is already a real subclass; no registration needed. # For an unrelated class, register it with the ABC: Serializer.register(ThirdPartySerializer)
Defensive patterns
Strategy: validation
Validate before calling
def register_no_cycle(abc, candidate):
if not isinstance(candidate, type):
raise TypeError('Can only register classes')
if issubclass(abc, candidate) and abc is not candidate:
raise ValueError(f'{candidate.__name__} is a base of {abc.__name__}; registration would cycle')
return abc.register(candidate) Type guard
def would_cycle(abc, candidate) -> bool:
return isinstance(candidate, type) and issubclass(abc, candidate) and abc is not candidate Try / catch
try:
Base.register(Other)
except RuntimeError as e:
if 'inheritance cycle' in str(e):
# direction is inverted; either drop it or reverse
if issubclass(Base, Other):
pass # already real subclass, registration unnecessary
else:
raise
else:
raise Prevention
- Convention: always call register on the ABC (the provider), passing the implementer as the argument
- Skip register entirely for real subclasses — it is a no-op
- In plugin systems, centralize registration in one helper that checks direction
When it happens
Trigger: Calling Sub.register(Super) where Sub actually inherits from Super (so issubclass(Sub, Super) is true) — e.g. class Serializable(ABC); class JSONSerializable(Serializable); then Serializable is fine, but JSONSerializable.register(Serializable) raises. Also virtual cycles: A.register(B) followed by B.register(A).
Common situations: Registration direction reversed by mistake in plugin systems ('register the base with the concrete class' instead of the opposite); decorator-based registration applied to a base class from a derived one; iterative refactoring where a base class moves under the ABC after registrations were written.
Related errors
- Can only register classes
- issubclass() arg 1 must be a class
- type 'datetime.timezone' is not an acceptable base type
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/7ce2d17476d65874.
Report an issue: GitHub.