You can feign immutablility on class attributes by playing with __setattr__. I don't remember the exact way to do it, but its roughly like this:
class YourClass:
def __setattr__(self, name, value):
if not hasattr(self, name):
super().__setattr__(name, value)
else:
# handle as you wish if the
# attr/value already exists.
# pass, raise, whatever
I say "feign immutability" because there are still cases in which an attr value can change, such as:
the underlying attribute contains a list and appending to the list
the underlying attribute is a class and modifying the class's attributes
pretty much anything that does "in place" changes, because so much of python is referential and has side effects.