Search code examples
pythonmypypython-typingpython-dataclasses

Type hint extra attributes (not fields)


This is almost a duplicate of Exclude some attributes from fields method of dataclass.

I have a dataclass that needs some extra attributes, which should not be considered as fields, that are derived from the fields during __post_init__. (In particular, they are not InitVar's!)

Since I type check my code, I want to add type hints for these attributes:

@dataclass
class MyDataClass:
    field0: int
    field1: int
    
    _: KW_ONLY
    
    fieldN: int

    # internal/derived attribute that shouldn't be considered as _fields_
    attr0: int  # we still want type hints for obvious reasons!
    attr1: int  # These are not InitVars!

However, this will make them fields. I tried adding single/double underscores, which didn't work. I considered making these @properties, but then where would you type hint the necessary self._attr0? I guess a @cached_property could work, but it's an unsatisfying answer.


Solution

  • Since dataclasses make real use in runtime of the typed variables, and you just want these to be set at static type checking time, probably the simplest idea is declare the fields in a way they are only "seen" at type-checking time.

    This way, the dataclass machinery won't know about them and won't create the fields, but when using them, the static type-checking tools will. The constante type.TYPE_CHECKING can be used for such a declaration:

    import typing
    from dataclasses import dataclass
    
    ...
    
    @dataclass
    class MyDataClass:
        field0: int
        field1: int
        
        _: KW_ONLY
        
        fieldN: int
    
        if typing.TYPE_CHECKING:
            # internal/derived attribute that shouldn't be considered as _fields_
            attr0: int  # we still want type hints for obvious reasons!
            attr1: int  # These are not InitVars!
    
    

    So, while that would be straightforward, from the feedback in the comments, it looks like the special-casing mypy uses to figure out dataclasses simply assume these are fields anyway, and require them to be present when instantiating MyDataClass.

    While it would be possible to create a mechanism to remove the fields from the class body and annotation before calling the @dataclass decorator, by creating an intermediary decorator - that would not help either: again, the fields would be visible to MyPy and it would require those to be present when creating the class, not knowing they are real fields.

    Just make them properties, and for type-hinting the inner self._attr0 attribute, just allow typing inference to do it, or typing.cast to force it when setting:

    
    class MyDataclass:
        ...
    
        def __post_init__(self):
            self._attr0 = ... # expression using existing parameters and attributes. 
                # Static typing _SHOULD_ know how to infer its type.       # but if it does not, force the type with cast:
             self._attr0 = typing.cast(int, <expression>) 
    
        @property
        def attr0(self) -> int:
             return self._attr0