Although I have never needed this, it just struck me that making an immutable object in Python could be slightly tricky. You can’t just override __setattr__
, because then you can’t even set attributes in the __init__
. Subclassing a tuple is a trick that works:
class Immutable(tuple): def __new__(cls, a, b): return tuple.__new__(cls, (a, b)) @property def a(self): return self[0] @property def b(self): return self[1] def __str__(self): return "<Immutable {0}, {1}>".format(self.a, self.b) def __setattr__(self, *ignored): raise NotImplementedError def __delattr__(self, *ignored): raise NotImplementedError
But then you have access to the a
and b
variables through self[0]
and self[1]
, which is annoying.
Is this possible in Pure Python? If not, how would I do it with a C extension?
(Answers that work only in Python 3 are acceptable).
Update:
As of Python 3.7, the way to go is to use the @dataclass
decorator, see the newly accepted answer.
Advertisement
Answer
Using a Frozen Dataclass
For Python 3.7+ you can use a Data Class with a frozen=True
option, which is a very pythonic and maintainable way to do what you want.
It would look something like that:
from dataclasses import dataclass @dataclass(frozen=True) class Immutable: a: Any b: Any
As type hinting is required for dataclasses’ fields, I have used Any from the typing
module.
Reasons NOT to use a Namedtuple
Before Python 3.7 it was frequent to see namedtuples being used as immutable objects. It can be tricky in many ways, one of them is that the __eq__
method between namedtuples does not consider the objects’ classes. For example:
from collections import namedtuple ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"]) ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"]) obj1 = ImmutableTuple(a=1, b=2) obj2 = ImmutableTuple2(a=1, c=2) obj1 == obj2 # will be True
As you see, even if the types of obj1
and obj2
are different, even if their fields’ names are different, obj1 == obj2
still gives True
. That’s because the __eq__
method used is the tuple’s one, which compares only the values of the fields given their positions. That can be a huge source of errors, specially if you are subclassing these classes.