12

In this code:

import dataclasses

@dataclasses.dataclass
class MyClass:
    value: str

obj = MyClass(value=1)

the dataclass MyClass is instantiated with a value that does not obey the value type.

Is there a simple way (using a decorator, an argument in the dataclass decorator or library) of enforcing the fields' types so that the last line in my example raises a ValueError or something like that? Is there a major downside of enforcing types in this way?

Jundiaius
  • 6,214
  • 3
  • 30
  • 43
  • 1
    Python is not typed and will not become typed. If you want to use typechecking try running [`mypy`](http://mypy-lang.org/). – Giacomo Alzetta Nov 22 '19 at 10:38
  • 3
    @Giacomo Doesn't mean you can't enforce types at runtime at all… – deceze Nov 22 '19 at 11:14
  • @deceze It means that you have to implement it yourself. The OP was specifically asking for something like "one line/switch" to turn on type checking. this isn't going to happen. – Giacomo Alzetta Nov 22 '19 at 12:03
  • I created a tiny Python library for this purpose: https://github.com/tamuhey/dataclass_utils This library can be applied for such dataclass that holds another dataclass (nested dataclass), and nested container type (like `Tuple[List[Dict...`) – tamuhey Feb 08 '21 at 03:10

1 Answers1

18

You can declare a custom __post_init__ method (see python's doc) and put all checks there to force type checking. This method can be declare in parent's class to reduce changes amount.

import dataclasses


@dataclasses.dataclass()
class Parent:
    def __post_init__(self):
        for (name, field_type) in self.__annotations__.items():
            if not isinstance(self.__dict__[name], field_type):
                current_type = type(self.__dict__[name])
                raise TypeError(f"The field `{name}` was assigned by `{current_type}` instead of `{field_type}`")

        print("Check is passed successfully")


@dataclasses.dataclass()
class MyClass(Parent):
    value: str


obj1 = MyClass(value="1")
obj2 = MyClass(value=1)

The results:

Check is passed successfully
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 3, in __init__
  File "<stdin>", line 7, in __post_init__
TypeError: The field `value` was assigned by `<class 'int'>` instead of `<class 'str'>`
MartenCatcher
  • 2,713
  • 8
  • 26
  • 39
  • 1
    this only checks the annotations of the current class not from any parents. Better to use the `dataclasses.fields` instead – Joost Döbken Jan 19 '21 at 12:24
  • 3
    Note that this doesn't work when importing `annotations` from `__future__`, then the `type` field becomes a string for builtin types – user107511 Oct 23 '21 at 14:16
  • 2
    Nice, and helpful. I needed to modify my types to avoid parameterized generics because `isinstance(["this"],list[str])` is not valid. – qrKourier Mar 11 '22 at 04:26