Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make class immutable in python? [duplicate]

I have read a lot about this subject here but i still can't find an appropriate answer. I have a class like:

class A(object):

    def __init__(self, first, second):
        self.first = first
        self.second = second

    def __eq__(self, other):
        return ****

    def __str__(self):
        return *****

    def __repr__(self):
        return **** 

a = A("a", "b")

How can i forbid a.first = "c" for example ?

like image 371
user253956 Avatar asked Dec 26 '22 11:12

user253956


2 Answers

You can override __setattr__ to either prevent any changes:

def __setattr__(self, name, value):
    raise AttributeError('''Can't set attribute "{0}"'''.format(name))

or prevent adding new attributes:

def __setattr__(self, name, value):
    if not hasattr(self, name):
        raise AttributeError('''Can't set attribute "{0}"'''.format(name))
    # Or whatever the base class is, if not object.
    # You can use super(), if appropriate.
    object.__setattr__(self, name, value)

You can also replace hasattr with a check against a list of allowed attributes:

if name not in list_of_allowed_attributes_to_change:
    raise AttributeError('''Can't set attribute "{0}"'''.format(name))

Another approach is to use properties instead of plain attributes:

class A(object):

    def __init__(self, first, second):
        self._first = first
        self._second = second

    @property
    def first(self):
        return self._first

    @property
    def second(self):
        return self._second
like image 55
chepner Avatar answered Dec 28 '22 06:12

chepner


You can disable __setattr__ as the last step of initializing the object.

class A(object):

    def __init__(self, first, second):
        self.first = first
        self.second = second
        self.frozen = True

    def __setattr__(self, name, value):
        if getattr(self, 'frozen', False):
            raise AttributeError('Attempt to modify immutable object')
        super(A, self).__setattr__(name, value)

>>> a = A(1, 2)
>>> a.first, a.second
(1, 2)
>>> a.first = 3

Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    a.first = 3
  File "<pyshell#41>", line 10, in __setattr__
    raise AttributeError('Attempt to modify immutable object')
AttributeError: Attempt to modify immutable object

Edit: This answer has a flaw, which I'm sure is shared by every other solution: if the members themselves are mutable, nothing protects them. If your object contains a list for example, it's all over. This is in contrast to i.e. C++ where declaring an object const extends to all its members recursively.

like image 25
Mark Ransom Avatar answered Dec 28 '22 05:12

Mark Ransom