Topics: properties, @property and property(), setters, getters
Updated 2020-10-04

Today, I digged a little deeper into the @property decorator, how it is related to the property() function and how its getter and setter methods work. These two links (link1, link2) were really helpful. Of course, there is also the official Python docs on the property() function.

The @property decorator

Yesterday we looked at decorators. The @property decorator allows us to create a read-only property. Let’s add a simple property to our CastleKilmereMember class.

import datetime 

class CastleKilmereMember:
    """ Creates a member of the Castle Kilmere School of Magic """
    ...
    @property
    def age(self) -> int:
        now = datetime.datetime.now().year
        return now - self.birthyear

This creates a property called age that can be accessed using the dot syntax:

bromley = CastleKilmereMember(name='Bromley Huckabee', birthyear=1959, sex='male')
print(bromley.age)

With the current year (2020) this outputs 61. Since the attribute is read-only we can’t change it:

bromley = CastleKilmereMember(name='Bromley Huckabee', birthyear=1959, sex='male')
bromley.age = 44

yields an AttributeError: can't set attribute. Of course, we can modify the behavior of @property. Specifically, we can define how a property is accessed, set and deleted by defining its getter, setter and deleter methods.

The relation between @property and property()

What you should always have in mind when using decorators is their syntax. In particular, remember that

@property
def age(self):
    now = datetime.datetime.now().year
    return now - self.birthyear

Is equivalent to

def age(self):
    now = datetime.datetime.now().year
    return now - self.birthyear

age = property(age)

The full signature of the property() function used in the second example (age = property(age)) is given by property(fget=None, fset=None, fdel=None, doc=None) -> property attribute.

  • fget is a function for getting the value of the attribute
  • fset is a function for setting the value of the attribute
  • fdel is a function for deleting the attribute

All these arguments are optional. So we can create a property object like we did above. But we can add extra “power” to it by specifying a setter, getter and/or deleter method. For example, we could use the setter method to implement certain constraints on the property value. Remember our _elms attribute in the Pupil class? Let’s create a property for this.

class Pupil(CastleKilmereMember):
    def __init__(self, name: str, birthyear: int, house: str, start_year: int):
        ...
        self._elms = {
                  'Critical Thinking': False,
                  'Self-Defense Against Fresh Fruit': False,
                  'Broomstick Flying': False,
                  'Magical Theory': False,
                  'Foreign Magical Systems': False,
                  'Charms': False,
                  'Defence Against Dark Magic': False,
                  'History of Magic': False,
                  'Potions': False,
                  'Transfiguration': False}

    @property
    def elms(self):
        return self._elms

Now, if we want to update the ELM’s of a student, we have to make sure that she actually passed the exam. Otherwise, the ELM can’t be awarded. Let’s implement that using a setter method.

    @elms.setter
    def elms(self, subject_and_grade):
        try:
            subject, grade = subject_and_grade
        except ValueError:
            raise ValueError("Pass an iterable with two items: subject and grade")

        passed = self.passed(grade)

        if passed:
            self._elms[subject] = True
        else:
            print('The exam was not passed so no ELM was awarded!')

Of course we can also delete the ELM’s of a student. But we should probably make sure that the user knows what he is doing when stealing a student all of her exams!

    @elms.deleter
    def elms(self):
        print("Caution, you are deleting this students' ELM's! "
              "You should only do that if she dropped out of school without passing any exam!")
        del self._elms