python: @property
source: http://python.jobbole.com/80955/ and http://www.blog.pythonlibrary.org/2014/01/20/python-201-properties/
Python has a neat little concept called a property that can do several useful things. In this article, we will be looking into how to do the following:
- Convert class methods into read-only
- Reimplement setters and getters into an attribute
In this article, you will learn how to use the builtin class property in several different ways. Hopefully by the end of the article, you will see how useful it is.
Getting Started
One of the simplest ways to use a property is to use it as a decorator of a method. This allows you to turn a class method into a class (read-only)
######################################################################## class Person(object): """""" #---------------------------------------------------------------------- def __init__(self, first_name, last_name): """Constructor""" self.first_name = first_name self.last_name = last_name #---------------------------------------------------------------------- @property def full_name(self): """ Return the full name """ return "%s %s" % (self.first_name, self.last_name)
In the code above, we create two class attributes or properties: self.first_name and self.last_name. Next we create a full_name method that has a @property decorator attached to it. This allows us to the following in an interpreter session:
>>> person = Person("Mike", "Driscoll")
>>> person.full_name
'Mike Driscoll'
>>> person.first_name
'Mike'
>>> person.full_name = "Jackalope"
Traceback (most recent call last):
File "<string>", line 1, in <fragment>
AttributeError: can't set attribute
As you can see, because we turned the method into a property, we can access it using normal dot notation. However, if we try to set the property to something different, we will cause an AttributeError to be raised, this is because the @property has made the method read only. The only way to change the full_name property is to do so indirectly:
>>> person.first_name = "Dan"
>>> person.full_name
'Dan Driscoll'
This is kind of limiting, so let’s look at another example where we can make a property that does allow us to set it.
Replacing Setters and Getters with a Python property
Let’s pretend that we have some legacy code that someone wrote who didn’t understand Python very well. If you’re like me, you’ve already seen this kind of code before:
from decimal import Decimal
########################################################################
class Fees(object):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._fee = None
#----------------------------------------------------------------------
def get_fee(self):
"""
Return the current fee
"""
return self._fee
#----------------------------------------------------------------------
def set_fee(self, value):
"""
Set the fee
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
To use this class, we have to use the setters and getters that are defined:
>>> f = Fees()
>>> f.set_fee("1")
>>> f.get_fee()
Decimal('1')
If you want to add the normal dot notation access of attributes to this code without breaking all the applications that depend on this piece of code, you can change it very simply by adding a property:
from decimal import Decimal
########################################################################
class Fees(object):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._fee = None
#----------------------------------------------------------------------
def get_fee(self):
"""
Return the current fee
"""
return self._fee
#----------------------------------------------------------------------
def set_fee(self, value):
"""
Set the fee
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
fee = property(get_fee, set_fee)
We added one line to the end of this code. Now we can do stuff like this:
>>> f = Fees()
>>> f.set_fee("1")
>>> f.fee
Decimal('1')
>>> f.fee = "2"
>>> f.get_fee()
Decimal('2')
As you can see, when we use property in this manner, it allows the fee property to set and get the value itself without breaking the legacy code. Let’s rewrite this code using the property decorator and see if we can get it to allow setting.
from decimal import Decimal
########################################################################
class Fees(object):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._fee = None
#----------------------------------------------------------------------
@property
def fee(self):
"""
The fee property - the getter
"""
return self._fee
#----------------------------------------------------------------------
@fee.setter
def fee(self, value):
"""
The setter of the fee property
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
#----------------------------------------------------------------------
if __name__ == "__main__":
f = Fees()
The code above demonstrates how to create a “setter” for the fee property. You can do this by decorating a second method that is also called fee with a decorator called @fee.setter. The setter is invoked when you do something like this:
>>> f = Fees()
>>> f.fee = "1"
If you look at the signature for property, it has fget, fset, fdel and doc as “arguments”. You can create another decorated method using the same name to correspond to a delete function using @fee.deleter if you want to catch the del command against the attribute.
Wrapping Up
Now you know how to use Python properties in your own classes. Hopefully you can find even more useful ways to use them in your own code.
Additional Reading
- Getters and setter in Python
- Official Python documentation on property
- A discussion on adding docstrings to a Python property on StackOverflow