Python Arena
Step 2 of 4

Getters with @property

A method that gives the value of a private attribute is called a getter, and a method that changes it a setter. With ordinary methods, the client has to write heater.get_target(), with parentheses. Python has a neater way: a property.

The @property decorator

A line that starts with @, just above a def, is a decorator. It changes how the method is used. With the decorator @property, the method is used like an attribute, without parentheses:

class Thermostat:
    def __init__(self):
        self.__target = 20

    @property
    def target(self):
        return self.__target


heater = Thermostat()
print(heater.target)
Output
20

The getter is named after the attribute, without the underscores. The client writes heater.target as if it were an attribute, but what runs is the method, which returns the private __target.

A property with only a getter is read-only. Trying to assign to it is an error:

class Thermostat:
    def __init__(self):
        self.__target = 20

    @property
    def target(self):
        return self.__target


heater = Thermostat()
heater.target = 45
Python stops with
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    heater.target = 45
    ^^^^^^^^^^^^^
AttributeError: property 'target' of 'Thermostat' object has no setter

The next step adds a setter, so the client can change the target too.

Properties that are calculated

A getter doesn't need an attribute of its own. It can work out its value every time it's asked:

class Temperature:
    def __init__(self, celsius):
        self.__celsius = celsius

    @property
    def celsius(self):
        return self.__celsius

    @property
    def fahrenheit(self):
        return self.__celsius * 9 / 5 + 32


today = Temperature(25)
print(today.celsius)
print(today.fahrenheit)
Output
25
77.0

The object stores only degrees Celsius, but the client can't tell celsius from fahrenheit: both look like attributes. That's encapsulation at work. The class decides what it stores, and could change that later without the client noticing.

No parentheses

A property is used without parentheses. With them, Python first gets the value (here the number 20) and then tries to call that number like a function:

class Thermostat:
    def __init__(self):
        self.__target = 20

    @property
    def target(self):
        return self.__target


heater = Thermostat()
print(heater.target())
Python stops with
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    print(heater.target())
          ~~~~~~~~~~~~~^^
TypeError: 'int' object is not callable

Your task

Challenge 1 of 3

Fill in the blank so that name is a property, and the program prints:

Grace

Challenge 2

What does this program print? Type each line it prints.

Challenge 3

Write the class Duration, which stores a length of time as a whole number of seconds in a private attribute. Its constructor takes the total number of seconds. It has these read-only properties:

  • total_seconds: the total number of seconds
  • hours: the full hours in it
  • minutes: the full minutes left over after the hours
  • seconds: the seconds left over after the minutes

It also has __str__, which returns a string such as 1 h 2 min 5 s. For example, this code:

film = Duration(3725)
print(film.total_seconds)
print(film.hours, film.minutes, film.seconds)
print(film)

prints:

3725
1 2 5
1 h 2 min 5 s