2

I want to create an immutable class like dictionary in python. I have found the following solution on stackoverflow, but this object is value can be updated by using __dict__.update function. Is there way to block this action.

class Immutable(object):
    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k, v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')


x = Immutable(a=5)
print(x.a) # 5 as expected
x.__dict__.update({'a': 7}) # should raise error or update copy of x
print(x.a) # 7, thus object is still mutable

Solution

As DeepSpace has mentioned in the comment to block access to __dict__ by implementing __getattr__.

I have implemented following solution and it worked

class Immutable(object):
    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k, v in kwargs.items():
            object.__setattr__(self, k, v)

    def __getattribute__(self, item):
        result = super(Immutable, self).__getattribute__(item)
        if item == '__dict__':
            return dict(**result)
        return result

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')


x = Immutable(a=5)
print(x.a) # 5
x.__dict__.update({'a': 7}) # update value on a copy of dict which has no effect
print(x.a) # 5 this time object value remain same
user5828964
  • 302
  • 2
  • 10

1 Answers1

0

You can add to slot the only property that you want in your class as under

class Immutable(object):
    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k, v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')

    __slots__ =('k')


x = Immutable(k=5)
print(x.k) # 5 as expected
x.__dict__.update({'k': 7}) # should raise error or update copy of x
print(x.a) # 7, thus object is still mutable

OUTPUT

5
Traceback (most recent call last):
  File "inmutable.py", line 24, in <module>
    x.__dict__.update({'k': 7}) # should raise error or update copy of x
AttributeError: 'Immutable' object has no attribute '__dict__'
MasterOfTheHouse
  • 1,164
  • 1
  • 14
  • 34
  • This will not solve my problem, because I don't want fixed number of attribute in the class, and after adding slots I can't add arbitrary attributes. – user5828964 Jan 14 '20 at 15:22
  • 1
    Just giving you another option . I think the answer you posted is the one that should be used most of the time, and also for your case, unless you are looking for some memory optimization because you are creating millions of this kind of immutable object. – MasterOfTheHouse Jan 14 '20 at 15:26