{"id":"encapsulation-getters","slug":"getters","title":"Getters with @property","text":"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**.\n\n### The @property decorator\n\nA 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:\n\n```python\nclass Thermostat:\n    def __init__(self):\n        self.__target = 20\n\n    @property\n    def target(self):\n        return self.__target\n\n\nheater = Thermostat()\nprint(heater.target)\n```\n\n```output\n20\n```\n\nThe 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`.\n\nA property with only a getter is **read-only**. Trying to assign to it is an error:\n\n```python\nclass Thermostat:\n    def __init__(self):\n        self.__target = 20\n\n    @property\n    def target(self):\n        return self.__target\n\n\nheater = Thermostat()\nheater.target = 45\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 11, in <module>\n    heater.target = 45\n    ^^^^^^^^^^^^^\nAttributeError: property 'target' of 'Thermostat' object has no setter\n```\n\nThe next step adds a setter, so the client can change the target too.\n\n### Properties that are calculated\n\nA getter doesn't need an attribute of its own. It can work out its value every time it's asked:\n\n```python\nclass Temperature:\n    def __init__(self, celsius):\n        self.__celsius = celsius\n\n    @property\n    def celsius(self):\n        return self.__celsius\n\n    @property\n    def fahrenheit(self):\n        return self.__celsius * 9 / 5 + 32\n\n\ntoday = Temperature(25)\nprint(today.celsius)\nprint(today.fahrenheit)\n```\n\n```output\n25\n77.0\n```\n\nThe 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.\n\n### No parentheses\n\nA 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:\n\n```python\nclass Thermostat:\n    def __init__(self):\n        self.__target = 20\n\n    @property\n    def target(self):\n        return self.__target\n\n\nheater = Thermostat()\nprint(heater.target())\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 11, in <module>\n    print(heater.target())\n          ~~~~~~~~~~~~~^^\nTypeError: 'int' object is not callable\n```\n","task":"Fill in the blank so that `name` is a property, and the program prints:\n\n```\nGrace\n```\n","kind":"fill","seed":"class Student:\n    def __init__(self, name):\n        self.__name = name\n\n    @[[property]]\n    def name(self):\n        return self.__name\n\n\nstudent = Student(\"Grace\")\nprint(student.name)\n","solution":"class Student:\n    def __init__(self, name):\n        self.__name = name\n\n    @property\n    def name(self):\n        return self.__name\n\n\nstudent = Student(\"Grace\")\nprint(student.name)\n","hints":["The line above the method is a decorator. Which one makes a method usable like an attribute?","The decorator is `@property`; the `@` is already there."],"tests":[{"name":"Output","stdin":"","expect":"Grace","hidden":false}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Box:\n    def __init__(self, width, height):\n        self.__width = width\n        self.__height = height\n\n    @property\n    def width(self):\n        return self.__width\n\n    @property\n    def area(self):\n        return self.__width * self.__height\n\n\nbox = Box(3, 5)\nprint(box.width)\nprint(box.area)\nprint(box.area + box.width)\n","solution":"class Box:\n    def __init__(self, width, height):\n        self.__width = width\n        self.__height = height\n\n    @property\n    def width(self):\n        return self.__width\n\n    @property\n    def area(self):\n        return self.__width * self.__height\n\n\nbox = Box(3, 5)\nprint(box.width)\nprint(box.area)\nprint(box.area + box.width)\n","hints":["`box.width` runs the getter `width`, which returns the private width.","`box.area` has no attribute of its own: the getter calculates 3 * 5 every time."],"tests":[{"name":"Output","stdin":"","expect":"3\n15\n18","hidden":false}],"require":[],"forbid":[],"lines":["3","15","18"]},{"task":"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:\n\n- `total_seconds`: the total number of seconds\n- `hours`: the full hours in it\n- `minutes`: the full minutes left over after the hours\n- `seconds`: the seconds left over after the minutes\n\nIt also has `__str__`, which returns a string such as `1 h 2 min 5 s`. For example, this code:\n\n```python\nfilm = Duration(3725)\nprint(film.total_seconds)\nprint(film.hours, film.minutes, film.seconds)\nprint(film)\n```\n\nprints:\n\n```\n3725\n1 2 5\n1 h 2 min 5 s\n```\n","kind":"code","seed":"class Duration:\n    def __init__(self, total_seconds):\n        self.__total = total_seconds\n\n\nif __name__ == \"__main__\":\n    film = Duration(3725)\n    print(film.total_seconds)\n    print(film.hours, film.minutes, film.seconds)\n    print(film)\n","solution":"class Duration:\n    def __init__(self, total_seconds):\n        self.__total = total_seconds\n\n    @property\n    def total_seconds(self):\n        return self.__total\n\n    @property\n    def hours(self):\n        return self.__total // 3600\n\n    @property\n    def minutes(self):\n        return self.__total % 3600 // 60\n\n    @property\n    def seconds(self):\n        return self.__total % 60\n\n    def __str__(self):\n        return f\"{self.hours} h {self.minutes} min {self.seconds} s\"\n\n\nif __name__ == \"__main__\":\n    film = Duration(3725)\n    print(film.total_seconds)\n    print(film.hours, film.minutes, film.seconds)\n    print(film)\n","hints":["Each property is a method with `@property` above it, which returns a number calculated from `self.__total`.","An hour is 3600 seconds, so the hours are `self.__total // 3600`. The seconds left over are `self.__total % 60`.","For the minutes, first take what's left after the full hours (`self.__total % 3600`), then divide that by 60 with `//`."],"tests":[{"name":"Test 1","stdin":"","call":"film = Duration(3725)\nprint(film.total_seconds)\nprint(film.hours, film.minutes, film.seconds)\nprint(film)\n","expect":"3725\n1 2 5\n1 h 2 min 5 s","hidden":false},{"name":"Test 2","stdin":"","call":"short = Duration(59)\nprint(short)\nprint(Duration(7200))\nprint(Duration(86399))\nprint(Duration(600).minutes)\n","expect":"0 h 0 min 59 s\n2 h 0 min 0 s\n23 h 59 min 59 s\n10","hidden":true}],"require":[{"pattern":"@property","min":4,"raw":true,"message":"Write the four values as properties, each with `@property` above it."}],"forbid":[]}]}