My son pointed me at PyCharm and, while poking through it’s built-in “intentions,” I discovered Python’s @property decorator, which led me to learn more about the Pythonic way to handle class properties. Unlike Java and C++, Python encourages you to create public class attributes (a/k/a properties). Here is an example:
class Hero(object): def __init__(self, **kwargs): self.name = kwargs['name'] if '__main__' == __name__: superman = Hero(name='Kal El') print superman.name superman.name = 'Clark Kent' print superman.name # Kal El # Clark Kent
That works while being beautifully readable. A hero has a name. You can print the name and, when the hero moves to a new planet, you can change the name.
Python makes it easy to change the behavior of the Hero class, and this is markedly different from what you can do in many other languages. Let’s say that you want the Hero class to store not just the hero’s name but also his first name and his last name. The obvious way to implement that would be in the “setter” for the name attribute. You would add some code to parse the name into first name and last name, and then store the two components separately. You might start with something like this:
class Hero(object): def __init__(self, **kwargs): self.set_name(kwargs['name']) def set_name(self, name): self.name = name self.firstname, self.lastname = self.name.split(' ', 2) if '__main__' == __name__: superman = Hero(name='Kal El') print superman.name print superman.firstname superman.name = 'Clark Kent' print superman.name print superman.firstname # Kal El # Kal # Clark Kent # Kal
The problem is that the simple assignment no longer works. Writing “superman.name = ‘Clark Kent'” no longer does what you expect; it only changes his name but does not change his firstname. Instead, you would need to write “superman.set_name(‘Clark Kent’)” which is painful; it requires you to hunt through your whole program and recode all occurrences of “superman.name =”.
This is where Python’s @property decorator comes in. It gives you a straightforward way to add behavior to the setter for the name attribute, so that you can still use “superman.name =” throughout your program.
class Hero(object): def __init__(self, **kwargs): self.name = kwargs['name'] @property def name(self): return '%s %s' % (self.firstname, self.lastname) @name.setter def name(self, name): self.firstname, self.lastname = name.split(' ', 2) if '__main__' == __name__: superman = Hero(name='Kal El') print superman.name print superman.firstname superman.name = 'Clark Kent' print superman.name print superman.firstname # Kal El # Kal # Clark Kent # Clark
Voila! Now the Hero class actually stores the firstname and the lastname while providing the illusion that it still has a simple attribute “name”. The @property decorator makes the syntax “superman.name” keep working to get the value of the name property. The @name.setter decorator, makes the syntax “superman.name =…” work to change the value of the hero’s name.
For more information on Python descriptors, see IBM’s excellent article, Introduction to Python descriptors.