The property decorator

Another thing that would be a crime not to mention is the property decorator. Imagine that you have an age attribute in a Person class and at some point you want to make sure that when you change its value, you're also checking that age is within a proper range, such as [18, 99]. You can write accessor methods, such as get_age() and set_age(...) (also called getters and setters), and put the logic there. get_age() will most likely just return age, while set_age(...) will also do the range check. The problem is that you may already have a lot of code accessing the age attribute directly, which means you're now up to some tedious refactoring. Languages like Java overcome this problem by using the accessor pattern basically by default. Many Java Integrated Development Environments (IDEs) autocomplete an attribute declaration by writing getter and setter accessor method stubs for you on the fly.

Python is smarter, and does this with the property decorator. When you decorate a method with property, you can use the name of the method as if it were a data attribute. Because of this, it's always best to refrain from putting logic that would take a while to complete in such methods because, by accessing them as attributes, we are not expecting to wait.

Let's look at an example:

# oop/property.py
class Person:
def __init__(self, age):
self.age = age # anyone can modify this freely

class PersonWithAccessors:
def __init__(self, age):
self._age = age

def get_age(self):
return self._age

def set_age(self, age):
if 18 <= age <= 99:
self._age = age
else:
raise ValueError('Age must be within [18, 99]')

class PersonPythonic:
def __init__(self, age):
self._age = age

@property
def age(self):
return self._age

@age.setter
def age(self, age):
if 18 <= age <= 99:
self._age = age
else:
raise ValueError('Age must be within [18, 99]')

person = PersonPythonic(39)
print(person.age) # 39 - Notice we access as data attribute
person.age = 42 # Notice we access as data attribute
print(person.age) # 42
person.age = 100 # ValueError: Age must be within [18, 99]

The Person class may be the first version we write. Then we realize we need to put the range logic in place so, with another language, we would have to rewrite Person as the PersonWithAccessors class, and refactor all the code that was using Person.age. In Python, we rewrite Person as PersonPythonic (you normally wouldn't change the name, of course) so that the age is stored in a private _age variable, and we define property getters and setters using that decoration, which allows us to keep using the person instances as we were before. A getter is a method that is called when we access an attribute for reading. On the other hand, a setter is a method that is called when we access an attribute to write it. In other languages, such as Java, it's customary to define them as get_age() and set_age(int value), but I find the Python syntax much neater. It allows you to start writing simple code and refactor later on, only when you need it, there is no need to pollute your code with accessors only because they may be helpful in the future.

The property decorator also allows for read-only data (no setter) and for special actions when the attribute is deleted. Please refer to the official documentation to dig deeper.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset