{"record":{"id":"7ce2d17476d65874","repo":"python/cpython","slug":"refusing-to-create-an-inheritance-cycle","errorCode":null,"errorMessage":"Refusing to create an inheritance cycle","messagePattern":"Refusing to create an inheritance cycle","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Lib/_py_abc.py","lineNumber":67,"sourceCode":"        cls._abc_cache = WeakSet()\n        cls._abc_negative_cache = WeakSet()\n        cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter\n        return cls\n\n    def register(cls, subclass):\n        \"\"\"Register a virtual subclass of an ABC.\n\n        Returns the subclass, to allow usage as a class decorator.\n        \"\"\"\n        if not isinstance(subclass, type):\n            raise TypeError(\"Can only register classes\")\n        if issubclass(subclass, cls):\n            return subclass  # Already a subclass\n        # Subtle: test for cycles *after* testing for \"already a subclass\";\n        # this means we allow X.register(X) and interpret it as a no-op.\n        if issubclass(cls, subclass):\n            # This would create a cycle, which is bad for the algorithm below\n            raise RuntimeError(\"Refusing to create an inheritance cycle\")\n        cls._abc_registry.add(subclass)\n        ABCMeta._abc_invalidation_counter += 1  # Invalidate negative cache\n        return subclass\n\n    def _dump_registry(cls, file=None):\n        \"\"\"Debug helper to print the ABC registry.\"\"\"\n        print(f\"Class: {cls.__module__}.{cls.__qualname__}\", file=file)\n        print(f\"Inv. counter: {get_cache_token()}\", file=file)\n        for name in cls.__dict__:\n            if name.startswith(\"_abc_\"):\n                value = getattr(cls, name)\n                if isinstance(value, WeakSet):\n                    value = set(value)\n                print(f\"{name}: {value!r}\", file=file)\n\n    def _abc_registry_clear(cls):\n        \"\"\"Clear the registry (for debugging or testing).\"\"\"\n        cls._abc_registry.clear()","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_py_abc.py#L49-L85","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":"# before\nclass Serializer(ABC): ...\nclass JSONSerializer(Serializer): ...\nJSONSerializer.register(Serializer)  # RuntimeError: cycle\n\n# after\n# JSONSerializer is already a real subclass; no registration needed.\n# For an unrelated class, register it with the ABC:\nSerializer.register(ThirdPartySerializer)","handlingStrategy":"validation","validationCode":"def register_no_cycle(abc, candidate):\n    if not isinstance(candidate, type):\n        raise TypeError('Can only register classes')\n    if issubclass(abc, candidate) and abc is not candidate:\n        raise ValueError(f'{candidate.__name__} is a base of {abc.__name__}; registration would cycle')\n    return abc.register(candidate)","typeGuard":"def would_cycle(abc, candidate) -> bool:\n    return isinstance(candidate, type) and issubclass(abc, candidate) and abc is not candidate","tryCatchPattern":"try:\n    Base.register(Other)\nexcept RuntimeError as e:\n    if 'inheritance cycle' in str(e):\n        # direction is inverted; either drop it or reverse\n        if issubclass(Base, Other):\n            pass  # already real subclass, registration unnecessary\n        else:\n            raise\n    else:\n        raise","preventionTips":["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"],"tags":["abc","inheritance","cycle","subclass"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}