Documenting Use of `abc.abstractmethod` in Non-`ABCMeta` Classes

The docs on @abc.abstractmethod currently states:

Using this decorator requires that the class’s metaclass is ABCMeta or is derived from it.

This is not actually true. The decorator can be applied to methods of non-abstract classes (and even free functions, although not very useful) without erroring at runtime (and ) — just that its abstractness won’t be counted when used outside of ABCMeta, and that there will be no runtime abstactness-checking when things are subclassed.

This makes the @abc.abstractmethod decorator somewhat usable in the same vein as type hints, when abstractness needs to be indicated in mixin classes or other situations where the ABCMeta metaclass can’t be used or is otherwise undesirable.

from abc import abstractmethod
class Superclass:    # Suppose due to metaclass conflict we can't use ABCMeta
    @abstractmethod    # Like type hints, decorator only used as indication, not runtime effect
    def foo(self) -> int:
        ...

    def bar(self) -> int:
        return self.foo() + 42

class Subclass(Superclass):
    def foo(self) -> int:
        return 10

print(Subclass().bar())    # Prints 52 as expected

Should this be considered an undocumented use of the API, or should this be considered legitimate (and the docs to be changed accordingly)? I’m not suggesting any runtime behavior change.

2 Likes

I’m not sure if this is what you are getting at or if it is orthogonal but mypy and pyright both consider this to be a type error:

from abc import ABC, abstractmethod

class T(ABC):
    @abstractmethod
    def f(self):
        pass

t = T() # Cannot instantiate abstract class T

However mypy and pyright disagree about this:

from abc import abstractmethod

class T:
    @abstractmethod
    def f(self):
        pass

t = T()

Then

$ pyright t.py
0 errors, 0 warnings, 0 informations
$ mypy t.py
t.py:8: error: Cannot instantiate abstract class "T" with abstract attribute "f"  [abstract]
Found 1 error in 1 file (checked 1 source file)

I would like it if both type checkers considered the latter to be an error. Specifically I want to use @abstractmethod for static typing but do not want to use ABCMeta at runtime. I think that if these things were being designed now then that is how @abstractmethod would work.

1 Like

This is a good reason to keep things orthogonal. That ABCs (and Protocols) are implemented as metaclasses is unfortunate, as I’m sure this has lead to various metaclass conflicts (and thus people’s avoiding using them) — the whole point of mixins is to be lightweight.

In my opinion type checkers should treat non-ABC classes with @abstractmethods declared within as “effectively an ABC” like how mypy currently is.

Meanwhile, ABC / ABCMeta should be used when the runtime behavior is desired. But a word of warning: Currently the runtime ABCMeta mechanism can’t see @abstractmethods declared in non-ABCMeta superclasses.

from abc import ABCMeta, abstractmethod

class T:
    @abstractmethod
    def f(self):
        return "f called"
    
class U(T, metaclass=ABCMeta):
    pass

print(U().f())    # Type check fails, runtime OK
1 Like

Currently I’d find it unusual if I saw @abstractmethod outside an ABC when reading code. But in their implementations, these decorators literally just add a marker attribute (ABCMeta counts them all up before deciding if it will allow initiation). cpython/Lib/abc.py at a2495ff1e7b370c26128aa41298edb9ff06b5666 · python/cpython · GitHub