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
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 _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: capruce@gmail.com
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