property class in Python
Getters and setters in Python can be used to set and get attributes. However, they don't strictly impose encapsulation, since attributes can be get and set without these getter-setter methods as well. Pythonic convention is that the users are trusted to access the attributes via getters-setters rather than mangling them directly. It is the Pythonic way.
An anti-Pythonic way to enforce encapsulation is to use the builtin property class and its decorator @property. The property class returns a property object. It is used to manage attributes. Its constructor takes four arguments:
- getter method of attribute x (keyword argument 'fget', defaults to None)
- setter method of attribute x (keyword argument 'fset', defaults to None)
- deleter method of attribute x (keyword argument 'fdel', defaults to None)
- docstring (help text) of attribute x (keyword argument 'doc', defaults to None)
The help(property) reveals a handy example. Consider a class C with an attribute 'x':
>>> class C(object): def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") >>> c = C() >>> c.x = 5 # invokes the setter setx() >>> c.x # invokes the getter getx() 5 >>> del c.x # invokes the deleter delx() >>> help(C) # Among other help information | x | I'm the 'x' property.
The @property decorator offers an easy-on-the-eye way of achieving the above functionality:
>>> class C(object): @property def x(self): "I am the 'x' property." return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x # @property implies that the function declared below (x) is passed to the property constructor as its first argument, return value (a property object) of which is assigned to the function itself i.e. x = property(fget = x). @x.setter translates to x = property(fset = x), the x on the right is the function declared after the @x.setter notation. Similarly, @x.deleter translates to x = property(fdel = x). # The 'doc' keyword argument of the property constructor gets set to the docstring of the function after the @property notation. So, in actuality, the @property gets translated to x = property(fget = x, fset = None, fdel = None, doc = "I am the 'x' property.")
Don't get confused by the attribute x and the property object x. The example in the documentation is ambiguous. The property object provides us with a handle to the attribute x. Here's a much cleaner example:
>>> class C(object): @property def propertyObjectOfAttrX(self): "I'm the 'x' property." return self._x @propertyObjectOfAttrX.setter def propertyObjectOfAttrX(self, value): self._x = value @propertyObjectOfAttrX.deleter def propertyObjectOfAttrX(self): del self._x >>> c = C() >>> c.propertyObjectOfAttrX = 5 >>> c.propertyObjectOfAttrX 5 >>> del c.propertyObjectOfAttrX
Note that in order for the @property decorator to work, the names of additional functions (setter and deleter) must be the same as the name of the property object i.e. the getter propertyObjectOfAttrX. Otherwise, you'll get an Attribute Error(s). I'll leave that to you to explore.
Read-only attribute
To make an attribute read-only, only specify the getter method of the attribute.
>>> class C(object): def __init__(self): self._x = 2 @property def propertyObjectOfAttrX(self): "I'm the 'x' property." return self._x >>> c = C() >>> c.propertyObjectOfAttrX 2 >>> c.propertyObjectOfAttrX = 10 Traceback (most recent call last): c.propertyObjectOfAttrX = 10 AttributeError: can't set attribute
This happens because the @property decorator call translates to propertyObjectOfAttrX = property(fget = propertyObjectOfAttrX, fset = None, fdel = None, doc = "I'm the 'x' property."). Unless the user knows the variable name( i.e. _x), he/she cannot set it. This is how the property objects enforce encapsulation.