Back to Ruff

Calling abstract methods on class objects

crates/ty_python_semantic/resources/mdtest/call/abstract_method.md

0.15.125.1 KB
Original Source

Calling abstract methods on class objects

Abstract classmethod with trivial body on class literal

Calling an abstract @classmethod with a trivial body directly on the class is unsound.

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int: ...

# snapshot: call-abstract-method
Foo.method()
snapshot
error[call-abstract-method]: Cannot call `method` on class object
 --> src/mdtest_snippet.py:4:5
  |
4 | /     @classmethod
5 | |     @abstractmethod
6 | |     def method(cls) -> int: ...
  | |_______________________________- Method `method` defined here
7 |
8 |   # snapshot: call-abstract-method
9 |   Foo.method()
  |   ^^^^^^^^^^^^ `method` is an abstract classmethod with a trivial body
  |

Abstract staticmethod with trivial body on class literal

Calling an abstract @staticmethod with a trivial body directly on the class is unsound.

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @staticmethod
    @abstractmethod
    def method() -> int: ...

# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()

Abstract classmethod via type[X] is allowed

When accessed via type[X], the runtime type could be a concrete subclass.

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int: ...

def f(x: type[Foo]):
    x.method()  # fine

Abstract staticmethod via type[X] is allowed

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @staticmethod
    @abstractmethod
    def method() -> int: ...

def f(x: type[Foo]):
    x.method()  # fine

Abstract classmethod with non-trivial body

An abstract method with a non-trivial body has a default implementation that can be called.

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int:
        return 42

Foo.method()  # fine

Abstract classmethod with pass body

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int:
        pass

# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()

Abstract classmethod with raise NotImplementedError body

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int:
        raise NotImplementedError

# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()

Concrete subclass calling inherited abstract classmethod

py
from abc import ABC, abstractmethod

class Base(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int: ...

class Derived(Base):
    @classmethod
    def method(cls) -> int:
        return 42

Derived.method()  # fine

Abstract subclass that does not override the abstract classmethod

If a subclass is still abstract (doesn't override the method), calling the inherited abstract classmethod on it is also unsound.

py
from abc import ABC, abstractmethod

class Base(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int: ...

class StillAbstract(Base):
    pass

# error: [call-abstract-method] "Cannot call `method` on class object"
StillAbstract.method()

Reversed decorator order

The @abstractmethod and @classmethod/@staticmethod decorators can appear in either order.

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @abstractmethod
    @classmethod
    def method(cls) -> int: ...

# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()

Abstract classmethod with docstring-only body

A method whose body is only a docstring is also a trivial body.

py
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def method(cls) -> int:
        """This method should be overridden."""
        ...

# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()

Implicitly abstract classmethod on Protocol

A classmethod on a Protocol class with a trivial body is implicitly abstract, even without @abstractmethod.

py
from typing import Protocol

class P(Protocol):
    @classmethod
    def method(cls) -> int: ...

# error: [call-abstract-method] "Cannot call `method` on class object"
P.method()

Implicitly abstract staticmethod on Protocol

py
from typing import Protocol

class P(Protocol):
    @staticmethod
    def method() -> int: ...

# error: [call-abstract-method] "Cannot call `method` on class object"
P.method()

Abstract classmethod defined in stub file

Methods defined in stub files are never considered to have trivial bodies, since stubs use ... as a placeholder regardless of the runtime implementation.

foo.pyi:

pyi
from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @abstractmethod
    def classmethod(cls) -> int: ...
    @staticmethod
    @abstractmethod
    def staticmethod() -> int: ...
py
from foo import Foo

# These should not trigger `call-abstract-method` since the methods are defined in a stub.
Foo.classmethod()
Foo.staticmethod()