The author of the dataclasses module made a conscious decision to not implement validators that are present in similar third party projects like attrs, pydantic, or marshmallow. And if your actual problem is within the scope of the one you posted, then doing the validation in the __post_init__ is completely fine.
But if you have more complex validation procedures or play with stuff like inheritance you might want to use one of the more powerful libraries I mentioned instead of dataclass. Just to have something to look at, this is what your example could look like using pydantic:
>>> from pydantic import BaseModel, validator
>>> class MyClass(BaseModel):
... is_good: bool = False
... is_bad: bool = False
...
... @validator('is_bad')
... def check_something(cls, v, values):
... if values['is_good'] and v:
... raise ValueError("Can not be both good and bad now, can it?")
... return v
...
>>> MyClass(is_good=True, is_bad=False) # this would be a valid instance
MyClass(is_good=True, is_bad=False)
>>> MyClass(is_good=True, is_bad=True) # this wouldn't
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "pydantic/main.py", line 283, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for MyClass
is_bad
Can not be both good and bad now, can it? (type=value_error)
is_bad: bool = field(init=False), then setself.is_bad = not self.is_goodin__post_init__.field(init=False)is also a very cool idea.