Python, provide type hints for a mixin that a property exists
Question:
For example this mixin:
from lib import stringlib
class NiceNameMixin:
@property
def nice_first_name(self):
return stringlib.clean_name(self.first_name)
@property
def nice_last_name(self):
return stringlib.clean_name(self.last_name)
warns me:
Cannot access member "first_name" for type "NiceNameMixin" Member "first_name" is unknown.
What is the correct was of telling the type checker that the attributes self.first_name
and self.last_name
will exist, and it’s okay?
Note I normally just # type: ignore
these kind of things, but I am starting to think there must be a more suitable way.
Note: Using Pyright
Update: Class property example for @chepner
from rest_framework import serializers
class UtilsMixin:
@classproperty
def model_class(cls: serializers.ModelSerializer): # Type of parameter Cls must be a supertype of its class "UtilsMixin"
return cls.Meta.model # Cannot access member "Meta" for type "ModelSerializer" Member "Meta" is unknown
Answers:
You can simply add annotated, but uninitialized, names to the class:
class NiceNameMixin:
first_name: str
last_name: str
@property
def nice_first_name(self):
return stringlib.clean_name(self.first_name)
@property
def nice_last_name(self):
return stringlib.clean_name(self.last_name)
This does not guarantee that a class inheriting from NiceNameMixin
will actually have those attributes, but it makes pyright
happy with NiceNameMixin
itself.
Adding to the excellent answer by @chepner. If one wants to express with types, that the child class has to implement the first_name
and last_name
attributes, consider annotating self
with an appropriate typing.Protocol
.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Protocol
class HasFirstAndLastName(Protocol):
first_name: str
last_name: str
class NiceNameMixin(ABC):
@property
def nice_first_name(self: HasFirstAndLastName) -> str:
return self.first_name.capitalize()
@property
def nice_last_name(self: HasFirstAndLastName):
return self.last_name.capitalize()
@dataclass
class User(NiceNameMixin):
first_name: str
last_name: str
@dataclass
class User2(NiceNameMixin):
first_name: str
User("John", "Wick").nice_first_name # OK
User2("John").nice_first_name # Err
For example this mixin:
from lib import stringlib
class NiceNameMixin:
@property
def nice_first_name(self):
return stringlib.clean_name(self.first_name)
@property
def nice_last_name(self):
return stringlib.clean_name(self.last_name)
warns me:
Cannot access member "first_name" for type "NiceNameMixin" Member "first_name" is unknown.
What is the correct was of telling the type checker that the attributes self.first_name
and self.last_name
will exist, and it’s okay?
Note I normally just # type: ignore
these kind of things, but I am starting to think there must be a more suitable way.
Note: Using Pyright
Update: Class property example for @chepner
from rest_framework import serializers
class UtilsMixin:
@classproperty
def model_class(cls: serializers.ModelSerializer): # Type of parameter Cls must be a supertype of its class "UtilsMixin"
return cls.Meta.model # Cannot access member "Meta" for type "ModelSerializer" Member "Meta" is unknown
You can simply add annotated, but uninitialized, names to the class:
class NiceNameMixin:
first_name: str
last_name: str
@property
def nice_first_name(self):
return stringlib.clean_name(self.first_name)
@property
def nice_last_name(self):
return stringlib.clean_name(self.last_name)
This does not guarantee that a class inheriting from NiceNameMixin
will actually have those attributes, but it makes pyright
happy with NiceNameMixin
itself.
Adding to the excellent answer by @chepner. If one wants to express with types, that the child class has to implement the first_name
and last_name
attributes, consider annotating self
with an appropriate typing.Protocol
.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Protocol
class HasFirstAndLastName(Protocol):
first_name: str
last_name: str
class NiceNameMixin(ABC):
@property
def nice_first_name(self: HasFirstAndLastName) -> str:
return self.first_name.capitalize()
@property
def nice_last_name(self: HasFirstAndLastName):
return self.last_name.capitalize()
@dataclass
class User(NiceNameMixin):
first_name: str
last_name: str
@dataclass
class User2(NiceNameMixin):
first_name: str
User("John", "Wick").nice_first_name # OK
User2("John").nice_first_name # Err