4

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 ?

Louis
  • 146,715
  • 28
  • 274
  • 320
user253956
  • 313
  • 6
  • 14
  • I think thats the answer you're looking for - http://stackoverflow.com/questions/4828080/how-to-make-an-immutable-object-in-python :) – olgierdh Mar 20 '14 at 20:45
  • This was the first think i read and I still can't apply it to my case. That is why i am posting my code. But still thanks. – user253956 Mar 20 '14 at 20:48

2 Answers2

5

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
chepner
  • 497,756
  • 71
  • 530
  • 681
5

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.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622