5

Having an instance c of a class C, I would like to make c immutable, but other instances of C dont have to.

Is there an easy way to achieve this in python?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
nuemlouno
  • 288
  • 1
  • 10

1 Answers1

8

You can't make Python classes fully immutable. You can however imitate it:

class C:
    _immutable = False
    def __setattr__(self, name, value):
        if self._immutable:
            raise TypeError(f"Can't set attribute, {self!r} is immutable.")
        super().__setattr__(name, value)

Example:

>>> c = C()
>>> c.hello = 123
>>> c.hello
123
>>> c._immutable = True
>>> c.hello = 456
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __setattr__
TypeError: Can't set attribute, <__main__.C object at 0x000002087C679D20> is immutable.

If you wish to set it at initialization, you can add an __init__ like so:

class C:
    _immutable = False
    def __init__(self, immutable=False):
        self._immutable = immutable
    def __setattr__(self, name, value):
        if self._immutable:
            raise TypeError(f"Can't set attribute, {self!r} is immutable.")
        super().__setattr__(name, value)

Keep in mind you can still bypass it by accessing and modifying the __dict__ of the instance directly:

>>> c = C(immutable=True)
>>> c.__dict__["hello"] = 123
>>> c.hello
123

You may attempt to block it like so:

class C:
    _immutable = False
    def __init__(self, immutable=False):
        self._immutable = immutable
    def __getattribute__(self, name):
        if name == "__dict__":
            raise TypeError("Can't access class dict.")
        return super().__getattribute__(name)
    def __setattr__(self, name, value):
        if self._immutable:
            raise TypeError(f"Can't set attribute, {self!r} is immutable.")
        super().__setattr__(name, value)

But even then it's possible to bypass:

>>> c = C(immutable=True)
>>> object.__getattribute__(c, "__dict__")["hello"] = 123
>>> c.hello
123
Bharel
  • 23,672
  • 5
  • 40
  • 80
  • 1
    This has the nice property that you can still set attributes in the `__init__`, as long as you set `_immutable` after (or at the end of) initialization. – rcriii Jan 18 '22 at 15:15
  • 1
    In your first example, `_immutable` is a class variable. Instead, it should be set as `self._immutable`. It's also better to set it from constructor parameter: `__init__(self, immutable=False)` instead of setting it directly from the outside – Expurple Jan 18 '22 at 15:16
  • @Expurple doesn't have to be set at `__init__`. I give the OP the option to work however he wants with it :-) – Bharel Jan 18 '22 at 15:17
  • 1
    @Bharel if OP wants an immutable instance, it doesn't make sense to implement this by mutating that instance. This should happen on construction, otherwise the instance is not immutable by definition – Expurple Jan 18 '22 at 15:19
  • @Expurple added that option :-) – Bharel Jan 18 '22 at 15:36