So I've got something like this:
from dataclasses import dataclass, field
from typing import ClassVar
import itertools
class MetaComponent(type):
iterator: ClassVar[itertools.count] = itertools.count(1)
def __new__(cls, clsname, bases, attrs):
attrs["type_of_part_index"] = next(MetaComponent.iterator)
return super().__new__(cls, clsname, bases, attrs)
class Component(metaclass=MetaComponent):
type_: str = field(init=False)
singleton: bool = True
def __post_init__(self):
self.type_ = type(self).__name__.lower()
@dataclass
class Tire(Component):
radius: float
singleton: bool = False
@dataclass
class Body(Component):
color: str
body = Body(color="red")
print(body.type_of_part_index)
MyPy gives me "Body" has no attribute "type_of_part_index"
. The code works as intended, so I'm wondering if there's a bug w/ MyPy or am I doing something incorrect w.r.t. adding an attribute like this. Is there a fix for this, or some more canonical way of writing the code such that MyPy sees the attribute correctly?
According to docs,
Mypy supports the lookup of attributes in the metaclass
but
Mypy does not and cannot understand arbitrary metaclass code.
The most common solution is to declare attribute type (either on metaclass or on derived class). Declaring on metaclass is better for DRY (no need to repeat this with each derived class), but worse for typechecking: trying to access MetaComponent.type_of_part_index
will be an error not visible to mypy. Declaring in derived class is opposite.
So you can do this:
class MetaComponent(type):
iterator: ClassVar[itertools.count] = itertools.count(1)
type_of_part_index: int # Will be cls attribute
# or ClassVar[int] or whatever you need
def __new__(cls, clsname, bases, attrs):
attrs["type_of_part_index"] = next(MetaComponent.iterator)
return super().__new__(cls, clsname, bases, attrs)
or this:
class Component(metaclass=MetaComponent):
type_of_part_index: int # Added by metaclass
type_: str = field(init=False)
singleton: bool = True
def __post_init__(self):
self.type_ = type(self).__name__.lower()
In this situation I would prefer the latter (and comment), because metaclass isn't reused.