If it’s 100x better than no types, then probably 10x better than C++ type system. It takes some time to unlearn using dicts everywhere, but then namedtuples become your best friend and noticeably improve maintainability. Probably the only place where python type system feels inadequate is describing json-like data near the point of its (de)serialization.
I don't get why I would choose a dataclass in cases where I've already decided that an ordinary tuple would be a better fit than a normal class (i.e. "anywhere you're tempted to use a namedtuple")
To me, namedtuples are a convenience to give a nicer syntax than ordinary tuples in scenarios where I don't want the overhead of having to store a copy of all the keys with every object, like a dict would. Dataclass seems to be even more stuff on top of a class which is effectively even more stuff on top of a dict, but all the use cases of namedtuples are those where you want much less stuff than an ordinary class has. And I don't want to have to define a custom class just as I often don't define a custom namedtuple in my code but use the one the database driver generates based on the query, which is a very common use case for namedtuples as efficient temporary storage of data that then gets processed to something else.
In general, preferring immutability is great. In Python specifically, it can be hard to pull off given that e.g. something as basic as dict does not have a standard immutable equivalent. You inevitably have to rely on conventions - basically saying "this is supposed to be immutable" rather than enforcing it.
You can use TypedDict from `typing_extensions` if your version doesn't have it. You can use a lot of the newer stuff from there, too, especially if you enable `__future__.annotations`.
How old is your Python, though? TypedDict is from 3.8. That was 5 years ago.
While that works (and I use it extensively), it's a bit hacky. You have to use `object.__setattr__` to set attributes in `__init__` or `__post_init__`, which looks so wrong.
I think the cleaner alternative would be to use a static or class method as an alternative constructor and use the init the dataclass decorator provides for you. Eg something like:
@dataclass(frozen=True)
class Foo:
bar: int
baz: str
@classmethod
def new(cls, bar: int) -> "Foo":
baz = calculate_baz(bar)
return cls(bar, baz)
foo = Foo.new(10)
There’s TyepdDict that is decent for a JSON like data structure if the types are simple. It doesn’t have the bells and whistles of Pydantic, but gets the job done for passing predictable dicts around and ensuring consistency while developing