Eric Jin
Eric Jin

Reputation: 3924

Class with read-only attributes

I want to create a class with attributes that can be __setattr__-ed by its methods internally, so an attempt like self.attr = value would raise an AttributeError. This is what I have so far:

class MyClass():
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

    def __repr__(self):
        return '%r class with a=%s, b=%s, c=%s' % (self, self.a, self.b, self.c)

    def __setattr__(self,attr,value):
        raise AttributeError('%r is read-only' % self)

    def setattr_(self,attr,value):
        self.attr = value


>>> obj = MyClass(1,2,3)
>>> obj.setattr_(a,4) # obj.a = 4
AttributeError: 'obj' is read-only # __setattr__ method also applies internally

Upvotes: 1

Views: 149

Answers (3)

VPfB
VPfB

Reputation: 17247

Please use the properties.

Anyway, it is good to understand the internals, here is a working code based on your question. Just to play with.

Once you redefine __setattr__ to fail, there is no way to set an attribute in that class. But there is still a working __setattr__ left in the parent class.

class MyClass():
    def __init__(self, a, b, c): 
        self.setattr_('a', a)
        self.setattr_('b', b)
        self.setattr_('c', c)

    def __setattr__(self,attr,value):
        raise AttributeError('%r is read-only' % self)

    def setattr_(self,attr,value):
        super().__setattr__(attr, value)

obj = MyClass(1,2,3)
obj.setattr_('a',4)  # note that a is a name (string)

Upvotes: 0

Rostan
Rostan

Reputation: 838

You can use properties in Python for this type of tasks. First, you make your attribute 'private' by adding two underscores, then you create a getter method with the @property decorator:

class MyClass:
    def __init__(self, a, b, c):
        self.__a, self.__b, self.__c = a, b, c

    @property
    def a(self):
        return self.__a

    @property
    def b(self):
        return self.__b

    @property
    def c(self):
        return self.__c

Now, you can use your class like this:

>>> my_object = MyClass('foo', 'bar', 'bar')
>>> print(my_object.b)
bar
>>> my_object.b = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

Note

I wrote 'private' because you can still access it if you really want:

>>> my_object._MyClass__b = 42
>>> print(my_object.b)
42

This has to do with the Zen of Python: "We’re all consenting adults here".

Upvotes: 0

chepner
chepner

Reputation: 530960

This is a use case for properties. Properties without a setter are read-only. In the following, a and b are read-only, while c is not.

class MyClass:
    def __init__(self, a, b, c):
        self._a = a
        self.b = b
        self._c = c

    # a is a read-only property
    @property
    def a(self):
        return self._a

    # b is an ordinary attribute

    # c is a property you can set
    @property
    def c(self):
        return self._c

    @c.setter
    def c(self, value):
        self._c = value

Since you have defined only getters for the a, attempts to change its value will fail. Attempts to change b will succeed as expected. Attempts to change c will succeed as if it were a regular attribute.

>>> obj = MyClass(1,2,3)
>>> obj.a = 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> obj.b = 5
>>> obj.c = 6
>>> obj.c
6

Upvotes: 1

Related Questions