This is an archived version of the course and is no longer updated. Please find the latest version of the course on the main webpage.

Property decorator

Now that you hopefully understand that decorators are, let us make sense of some of the decorators provided by Python.

The main one that you might have encountered is the @property decorator.

I first introduced this back in Week 1 when I discussed encapsulation in OOP. Let us revisit this now that you have had more experience with OOP, and also understand what decorators are.

As a recap, there are times in OOP when you do not want to expose your attributes directly. For example, you might not want someone else to modify your attributes directly (maybe you want to ensure that the values are legal). Or you want to embellish your attributes before allowing someone to access it (i.e. putting on some makeup). Sort of like you putting on a face mask before going out, in case someone passes a certain virus to you!

For example, let’s say we have instances of VainPerson who wish to always be addressed by a long title. And the name must always be stored in uppercase, and the VainPerson will only allow their name to be changed if they like the sound of the new name. We can implement this by keeping the __name attribute private (double underscores indicate that a variable is private), and using getter and setter methods to access __name in a controlled manner (this is the way you would do it in Java).

class VainPerson:
    def __init__(self, name):
        self.set_name(name)

    def get_name(self):
        return f"Your Most Honourable Excellency {self.__name}"

    def set_name(self, name):
        if self.likes_name(name):
            self.__name = name.upper()
        else:
            # Just so we have a default name
            if not self.__name:
                self.__name = "JOSIAH"
        
    def likes_name(self, name):
        if name.upper() in ["EDWARD", "VICTORIA", "WILLIAM", "GEORGE", "ELIZABETH", "JOSIAH"]:
            return True
        else:
            return False

 
lovely_person = VainPerson("EDWARD")
print(lovely_person.get_name()) # Your Most Honourable Excellency EDWARD
print(lovely_person.__name)  # AttributeError: 'VainPerson' object has no attribute '__name'
lovely_person.set_name("loser") 
print(lovely_person.get_name()) # Your Most Honourable Excellency EDWARD
lovely_person.set_name("george")
print(lovely_person.get_name()) # Your Most Honourable Excellency GEORGE
lovely_person.__name = "Liz"    # __name is private. Not set to "Liz"
print(lovely_person.get_name()) # Your Most Honourable Excellency GEORGE

Properties

In the above code, get_name() and set_name() are clunky hard to read. It would have been nicer if we could just simply use:

lovely_person.name = "Edward"
print(lovely_person.name)

Can we still access the name attribute this way, but without exposing the attribute?

The answer is yes. All we need to do is to create a property object called name, and link the property to the getter method get_name() and the setter method set_name(). Here is a cleaner version of the code from above.

class VainPerson:
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return f"Your Most Honourable Excellency {self.__name}"

    def set_name(self, name):
        if self.likes_name(name):
            self.__name = name.upper()
        else:
            # Just so we have a default name
            if not self.__name:
                self.__name = "JOSIAH"

    def likes_name(self, name):
        if name.upper() in ["EDWARD", "VICTORIA", "WILLIAM", "GEORGE", "ELIZABETH", "JOSIAH"]:
            return True
        else:
            return False

    name = property(get_name, set_name)


lovely_person = VainPerson("EDWARD")
print(lovely_person.name) # Your Most Honourable Excellency EDWARD
lovely_person.name = "loser"
print(lovely_person.name) # Your Most Honourable Excellency EDWARD
lovely_person.name = "george"
print(lovely_person.name) # Your Most Honourable Excellency GEORGE

Much nicer to read, is it not?

The property class has a constructor, which takes a function (the getter method) as the first parameter, and optionally a setter method as the second parameter. Python will invoke the correct methods when a user accesses the .name attribute of a VainPerson instance.

If you omit the setter method (the second argument), then your property becomes a read-only property.

class VainPerson:
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return f"Your Most Honourable Excellency {self.__name}"

    name = property(get_name)

lovely_person = VainPerson("EDWARD")
print(lovely_person.name) # Your Most Honourable Excellency EDWARD
lovely_person.name = "loser"  # AttributeError: can't set attribute

Property decorator

Now, there is still too much repetition there. We still have to bind the name property to the getter method get_name and setter method set_name. Can we just simplify this?

Notice the line name = property(get_name). Does that remind you of something we talked about earlier?

Like decorators?

So let’s use decorators to make our code even more readable!

class VainPerson:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return f"Your Most Honourable Excellency {self.__name}"

So the code above is equivalent to the one before with name = property(get_name). Note that the method name now has the same name as the property name (name in our case).

To also assign a setter method to this property, we use a @property_name.setter decorator.

class VainPerson:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return f"Your Most Honourable Excellency {self.__name}"
    
    @name.setter
    def name(self, new_name):
        self.__name = new_name.upper()

So the code above is equivalent to the one with name = property(get_name, set_name).

Note that the method name for the setter also has the same name as the property name. You also need to use the property name in the decorator.

Now, users of your VainPerson class can both read and write to the name property, and you also made your code more readble in the process!