There are different way to prevent setting attributes and make attributes read only on object in python. We can use any one of the following way to make attributes readonly
- Property Descriptor
- Using descriptor methods __get__ and __set__
- Using slots (only restricts setting arbitary attributes)
Python ships with built in function called property. We can use this function to customize the way attributes be accessed and assigned.
First I will explain you about property before I get you idea about how it is useful to make attribute readonly.
Typical signature of the function property is
property([fget[, fset[, fdel[, doc]]]]
As you can see here this function take four arguments, those are
fget is a function for getting an attribute value. fset is a function for setting an attribute value. fdel is a function for deleting an attribute value. And doc creates a docstring for the attribute.
All these function are for the sake of single attribute. That is fget function will be called when you access/get the attribute. fset function will be called when you are trying to set the attribute.
class Foo(object): def __init__(self): self._x = None def getx(self): print "Getting attribute x" return self._x def setx(self, value): print "Setting attribute x" self._x = value def delx(self): print "Deleting attribue x" del self._x x = property(getx, setx, delx, "I'm the 'x' property.")
Instantiate Foo and try to play the instance attribute x
>>> i = Foo() >>> i.x Getting attribute x >>> i.x = 3 Setting attribute x >>> i.x Getting attribute x >>> i._x #Still you can access hidden attrib _x Where it is abstracted as x 3 >>> del i.x Deleting attribue x
I hope, you got what exactly the function property is and how we use it. In many cases we use this property to hide actual attributes and abstract them with another name.
You can use property as decorator also. Something like
class Foo(object): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
Now let’s come to actual thing how we make attribute readonly.
It’s simple you just don’t define setter for the property attribute. Let’s see the following example
class Bank(object): def __init__(self): self._money = 100000 @property def money(self): """Get the money available.""" return self._money
>>> b = Bank() >>> b.money 100000 >>> b.money = 9000000 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute >>> >>> >>> del b.money Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't delete attribute
Here, as we didn’t define setter for the property attribute. So python won’t allow setting that specific attribute even you can’t delete if you don’t define fdel. Thus, attribute becomes read only. Still you can access b._money and you can set that attribute there is no restriction over setting this internal attribute.
Descriptor methods __get__ and __set__
These magic methods define descriptor for the object attribute. To get complete understanding and usage about descriptor magic methods, please check other article .
Like fget and fset functions that property function takes, __get__ is used to define behavior when descriptor’s value is retrieved. __set__ method is used to define behavior when descriptor value is getting set(assigned). Where __delete__ is used to define behavior when descriptor is getting deleted.
To restrict setting attribute and make it readonly. You have to use __set__ magic method of descriptor and raise exception in it.
Let’s see the simple example demonstrating descriptor object and readonly attributes using descriptors
class Distance(object): """Descriptor for a distance. Distance in meters""" def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Time(object): """Descriptor for a time.""" def __init__(self, value=1.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Speed(object): """Descriptor for a speed.""" def __get__(self, instance, owner): speed = instance.distance / instance.time return "%s m/s" % speed def __set__(self, instance, value): ## Restrict setting speed attribute raise AttributeError, "can not set attribute seepd" class Vehicle(object): """ Class to represent vehicle holding three descriptors for speed. Where speed is readonly """ distance = Distance() time = Time() speed = Speed()
Lets see the result and trying to set speed attribute
>>> from python_property import Vehicle >>> v = Vehicle() >>> v.distance = 100 >>> v.time = 5 >>> v.speed '20.0 m/s' >>> v.speed = 40 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "python_property.py", line 98, in __set__ raise AttributeError, "can not set attribute seepd" AttributeError: can not set attribute seepd >>>
As you can see here, we can’t set the attribute speed on instance v of Vehicle. Because we are restricting it in descriptor method __set__ of class Speed
The basic usage of __slots__ is to save space in objects. Instead of having a dynamic dict that allows adding attributes to objects at anytime, there is a static structure which does not allow additions after creation. This will also gain us some performance due to lack of dynamic attribute assignment. That is, it saves the overhead of one dict for every object that uses slots.
Think of you are creating lot of (hundreds, thousands) instances from the same class, this could be useful as memory and performance optimization tool.
If you are using __slots__ means you are defining static attributes on class. This is how we save memory and gain performance as there is not dynamic attribute assignment. Thus you can’t set new attributes on object.
>>> class Foo(object): ... __slots__ = 'a', 'b' ... >>> i = Foo() >>> i.a = 3 >>> i.c = 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Foo' object has no attribute 'c' >>>
You see, in the above example we are not able to set attribute c as it not given in __sots__. Any way it’s about restricting assignment to new attributes and you can combine either above two methods to make existing attributes readonly.
 __get__ and __set__ data descriptors don’t work on instance attributes http://stackoverflow.com/questions/23309698/why-is-the-descriptor-not-getting-called-when-defined-as-instance-attribute