Abstract attributes for Mixins

When working with mixins, I often wish I could specify abstract attributes – to have the contract “if the class provides these attributes, then this mixin provides this functionality.” Currently, it’s possible to write the following: class ProvidesLength(ABC): # my mixin data: list[float] def __len__(self) -> int: return len(self.data) @dataclass class A(ProvidesLength): data: list[float] = field(default_factory=list) class B(ProvidesLength): def __init__(self, data: list[float] | None = None): self.data = [] if data is None else data class C(ProvidesLength): @property def data(self) -> list[float]: # incompatible with supertype return [0.0, 1.0, 2.0] However, type checkers will not warn me if the concrete class doesn’t specify the `data` attribute in the above, because type checkers treat the type declaration `data: list[float]` like a variable definition. Ideally, there’d be some way to say that an attribute is abstract, like we can do with methods. It might look like this: from typing import ABSTRACT class ProvidesLength(ABC): data: list[float] = ABSTRACT def __len__(self) -> int: return len(self.data) but I’m not attached to any syntax. I know that I can use abstract properties to get something similar: class LengthMixin(ABC): @property @abstractmethod def data(self) -> list[float]: ... def __len__(self) -> int: return len(self.data) @dataclass class A(LengthMixin): data_: list[float] = field(default_factory=list) @property def data(self) -> list[float]: return self.data_ class B(LengthMixin): def __init__(self, data: list[float] | None = None): self._data = [] if data is None else data @property def data(self) -> list[float]: return self._data But this is just very verbose and doesn’t play that well with dataclasses (I can’t use a dataclass field to implement the abstract property). Another thing that actually works with pyright is to use a Protocol instead of an ABC: class ProvidesLength(Protocol): data: list[float] def __len__(self) -> int: return len(self.data) class A(ProvidesLength): # pyright complains about missing `data` data2: list[float] But Protocols do not feel like the appropriate concept to use for mixins. Besides, it doesn’t work in other type checkers I’ve tried (mypy and pycharm). Other possible syntaxes for abstract attributes: class Mixin(ABC): a: Abstract[list[float]] # wrapping in Abstract[] b: list[float] = ... # ellipses as value Best, Thomas

It feels like this is exactly what a protocol is for (imo). You can declare the type of the self parameter in a mixin to be a protocol to limit what it can be mixed into. The main issue is that it only raises an error when you go to use something from the mixin - just inheriting from it won't raise an error. ```python from dataclasses import dataclass from typing import Protocol class HasAge(Protocol): age: int class PrintAgeMixin: def print_age(self: HasAge) -> None: print(self.age) @dataclass class Person: name: str @dataclass class PersonWithAge(PrintAgeMixin, Person): age: int @dataclass class PersonWithoutAge(PrintAgeMixin, Person) pass PersonWithAge("John", 50).print_age() PersonWithoutAge("John").print_age() # error: Invalid self argument "PersonWithoutAge" to attribute function "print_age" with type "Callable[[HasAge], None]""Callable[[HasAge], None]" ``` You can try it on the mypy playground: https://mypy-play.net/?mypy=latest&python=3.11&gist=cd3e24db42cf71e8fcdefc380053f645 - Luis On Fri, May 27, 2022 at 8:26 PM Thomas Kehrenberg <tmke@posteo.net> wrote:

I've written a library for abstract class attributes: https://github.com/antonagestam/abcattrs, it requires decorating the base class to get errors at runtime, and it has no support for static type checking. It would be interesting to explore implementing a mypy plugin for static type checking support though.

It feels like this is exactly what a protocol is for (imo). You can declare the type of the self parameter in a mixin to be a protocol to limit what it can be mixed into. The main issue is that it only raises an error when you go to use something from the mixin - just inheriting from it won't raise an error. ```python from dataclasses import dataclass from typing import Protocol class HasAge(Protocol): age: int class PrintAgeMixin: def print_age(self: HasAge) -> None: print(self.age) @dataclass class Person: name: str @dataclass class PersonWithAge(PrintAgeMixin, Person): age: int @dataclass class PersonWithoutAge(PrintAgeMixin, Person) pass PersonWithAge("John", 50).print_age() PersonWithoutAge("John").print_age() # error: Invalid self argument "PersonWithoutAge" to attribute function "print_age" with type "Callable[[HasAge], None]""Callable[[HasAge], None]" ``` You can try it on the mypy playground: https://mypy-play.net/?mypy=latest&python=3.11&gist=cd3e24db42cf71e8fcdefc380053f645 - Luis On Fri, May 27, 2022 at 8:26 PM Thomas Kehrenberg <tmke@posteo.net> wrote:

I've written a library for abstract class attributes: https://github.com/antonagestam/abcattrs, it requires decorating the base class to get errors at runtime, and it has no support for static type checking. It would be interesting to explore implementing a mypy plugin for static type checking support though.
participants (3)
-
Anton Agestam
-
Luis Miguel Morera De La Cruz
-
Thomas Kehrenberg