{"id":"access-modifiers-private","slug":"private-in-subclass","title":"Private attributes and derived classes","text":"In the module on encapsulation you made attributes **private** by starting their names with two underscores. A private attribute can only be used in the class's own methods. The class's **clients**, the code that uses its objects, can't reach it, which keeps the object's data safe from careless changes.\n\nInheritance raises a new question: is a derived class like the class's own methods, or like any other client? For private attributes, it's like any other client. A derived class can't use its base class's private attributes either:\n\n```python\nclass Playlist:\n    def __init__(self, name):\n        self.name = name\n        self.__songs = []\n\n    def add(self, title, seconds):\n        self.__songs.append((title, seconds))\n\n    def count(self):\n        return len(self.__songs)\n\n\nclass TimedPlaylist(Playlist):\n    def duration(self):\n        total = 0\n        for title, seconds in self.__songs:\n            total += seconds\n        return f\"{total // 60} min {total % 60} s\"\n\n\nmix = TimedPlaylist(\"Road trip\")\nmix.add(\"Highway Song\", 215)\nmix.add(\"Slow River\", 187)\nprint(mix.count())\nprint(mix.duration())\n```\n\n```output\n2\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 25, in <module>\n    print(mix.duration())\n          ~~~~~~~~~~~~^^\n  File \"main.py\", line 16, in duration\n    for title, seconds in self.__songs:\n                          ^^^^^^^^^^^^\nAttributeError: 'TimedPlaylist' object has no attribute '_TimedPlaylist__songs'. Did you mean: '_Playlist__songs'?\n```\n\nThe playlist keeps each song as a pair of a title and a length in seconds. `add` and `count` work, because they are `Playlist`'s own methods. `duration`, which adds up the lengths, fails.\n\n### What the error message tells you\n\nThe message shows how Python hides a private attribute: it quietly renames it, putting the class's name in front. This renaming is called **name mangling**. Inside the class `Playlist`, `self.__songs` really means `self._Playlist__songs`. Inside `TimedPlaylist`, the same words `self.__songs` mean `self._TimedPlaylist__songs`, and the playlist has no attribute of that name.\n\nPython's \"Did you mean\" points at the real name, `_Playlist__songs`. Writing that would work, but don't: it goes around the lock on purpose, and the code breaks as soon as someone renames the class. The renaming has a good side, too. A derived class can have a private attribute of its own with the same name as one in its base class, and the two never get mixed up.\n\n### Go through the public methods\n\nA derived class can use everything public in its base class, just as any client can. So if the base class offers a method or a property that gives the data, the derived class uses that:\n\n```python\nclass Playlist:\n    def __init__(self, name):\n        self.name = name\n        self.__songs = []\n\n    def add(self, title, seconds):\n        self.__songs.append((title, seconds))\n\n    def songs(self):\n        # A copy, so the private list stays safe.\n        return self.__songs[:]\n\n\nclass TimedPlaylist(Playlist):\n    def duration(self):\n        total = 0\n        for title, seconds in self.songs():\n            total += seconds\n        return f\"{total // 60} min {total % 60} s\"\n\n\nmix = TimedPlaylist(\"Road trip\")\nmix.add(\"Highway Song\", 215)\nmix.add(\"Slow River\", 187)\nmix.add(\"River Bend\", 242)\nprint(mix.duration())\n```\n\n```output\n10 min 44 s\n```\n\nThis is often the best way: the base class keeps control of its data, and the derived class only asks for it. When a base class has no such method and its derived classes really need the data, the base class can share it in another way, which is the topic of the next step.\n","task":"The method `highest` of `MaxThermometer` stops with an `AttributeError`, because it uses the private attribute of its base class. Fix `highest` so that it gets the readings through `Thermometer`'s public method `readings()` instead. Don't change `Thermometer`. For example,\n\n```python\nthermometer = MaxThermometer()\nthermometer.add(12)\nthermometer.add(17)\nthermometer.add(9)\nprint(thermometer.highest())\nprint(thermometer.readings())\n```\n\nshould print:\n\n```\n17\n[12, 17, 9]\n```\n","kind":"code","seed":"class Thermometer:\n    def __init__(self):\n        self.__readings = []\n\n    def add(self, reading):\n        self.__readings.append(reading)\n\n    def readings(self):\n        return self.__readings[:]\n\n\nclass MaxThermometer(Thermometer):\n    def highest(self):\n        return max(self.__readings)\n\n\nif __name__ == \"__main__\":\n    thermometer = MaxThermometer()\n    thermometer.add(12)\n    thermometer.add(17)\n    print(thermometer.highest())\n","solution":"class Thermometer:\n    def __init__(self):\n        self.__readings = []\n\n    def add(self, reading):\n        self.__readings.append(reading)\n\n    def readings(self):\n        return self.__readings[:]\n\n\nclass MaxThermometer(Thermometer):\n    def highest(self):\n        return max(self.readings())\n\n\nif __name__ == \"__main__\":\n    thermometer = MaxThermometer()\n    thermometer.add(12)\n    thermometer.add(17)\n    print(thermometer.highest())\n","hints":["`self.__readings` in `MaxThermometer` means `self._MaxThermometer__readings`, which doesn't exist. The derived class has to ask for the readings like any client.","`self.readings()` calls the public method `MaxThermometer` inherited, and it returns a list of the readings. Give that list to `max`."],"tests":[{"name":"Test 1","stdin":"","call":"thermometer = MaxThermometer()\nthermometer.add(12)\nthermometer.add(17)\nthermometer.add(9)\nprint(thermometer.highest())\nprint(thermometer.readings())\n","expect":"17\n[12, 17, 9]","hidden":false},{"name":"Test 2","stdin":"","call":"thermometer = MaxThermometer()\nthermometer.add(-5)\nthermometer.add(-11)\nprint(thermometer.highest())\nthermometer.add(3)\nprint(thermometer.highest())\n","expect":"-5\n3","hidden":true}],"require":[{"pattern":"self\\.__readings","min":3,"max":3,"message":"Keep `Thermometer` as it is, and don't use `self.__readings` in `MaxThermometer`: ask for the readings with `self.readings()`."}],"forbid":[{"pattern":"_Thermometer__","message":"Don't go around the lock with the renamed attribute: use the public method `readings()`."}],"more":[{"task":"A kid's wallet is a wallet where at most 20 euros can be spent at a time. Write a class `KidsWallet` that inherits `Wallet`, with these methods:\n\n- `can_buy(price)` returns `True` if the price is at most 20 and the wallet has at least that much money, and otherwise `False`.\n- `spend(amount)` overrides `Wallet`'s: an amount over 20 isn't spent, and it returns `False`. Any other amount is handled by `Wallet`'s `spend`.\n\nThe money is private in `Wallet`, so use its property `money` and `super()`. For example,\n\n```python\nwallet = KidsWallet(30)\nprint(wallet.can_buy(25))\nprint(wallet.spend(25))\nprint(wallet.spend(15))\nprint(wallet.can_buy(18))\nprint(wallet.money)\n```\n\nshould print:\n\n```\nFalse\nFalse\nTrue\nFalse\n15\n```\n","kind":"code","seed":"class Wallet:\n    def __init__(self, money):\n        self.__money = money\n\n    @property\n    def money(self):\n        return self.__money\n\n    def spend(self, amount):\n        if amount > self.__money:\n            return False\n        self.__money -= amount\n        return True\n\n\n# Write the class KidsWallet here.\n\n\nif __name__ == \"__main__\":\n    wallet = KidsWallet(30)\n    print(wallet.can_buy(25))\n    print(wallet.spend(15))\n    print(wallet.money)\n","solution":"class Wallet:\n    def __init__(self, money):\n        self.__money = money\n\n    @property\n    def money(self):\n        return self.__money\n\n    def spend(self, amount):\n        if amount > self.__money:\n            return False\n        self.__money -= amount\n        return True\n\n\nclass KidsWallet(Wallet):\n    def can_buy(self, price):\n        return price <= 20 and price <= self.money\n\n    def spend(self, amount):\n        if amount > 20:\n            return False\n        return super().spend(amount)\n\n\nif __name__ == \"__main__\":\n    wallet = KidsWallet(30)\n    print(wallet.can_buy(25))\n    print(wallet.spend(15))\n    print(wallet.money)\n","hints":["`KidsWallet` needs no constructor of its own. Inside its methods, `self.money` gives the money through `Wallet`'s property, while `self.__money` wouldn't work.","`can_buy` checks two things: `price <= 20` and `price <= self.money`. Both must be true.","In `spend`, return `False` for an amount over 20. Otherwise return what `super().spend(amount)` returns: `Wallet`'s version knows how to change the private money."],"tests":[{"name":"Test 1","stdin":"","call":"wallet = KidsWallet(30)\nprint(wallet.can_buy(25))\nprint(wallet.spend(25))\nprint(wallet.spend(15))\nprint(wallet.can_buy(18))\nprint(wallet.money)\n","expect":"False\nFalse\nTrue\nFalse\n15","hidden":false},{"name":"Test 2","stdin":"","call":"wallet = KidsWallet(100)\nprint(wallet.can_buy(20))\nprint(wallet.spend(20))\nprint(wallet.spend(21))\nprint(wallet.money)\n","expect":"True\nTrue\nFalse\n80","hidden":true},{"name":"Test 3","stdin":"","call":"wallet = KidsWallet(5)\nprint(wallet.can_buy(5))\nprint(wallet.spend(6))\nprint(wallet.spend(5))\nprint(wallet.money)\n","expect":"True\nFalse\nTrue\n0","hidden":true}],"require":[{"pattern":"class\\s+KidsWallet\\s*\\(\\s*Wallet\\s*\\)","message":"`KidsWallet` should inherit `Wallet`."},{"pattern":"super\\(\\)\\.spend\\(","message":"Let `Wallet`'s `spend` change the money: call it with `super().spend(...)`."}],"forbid":[{"pattern":"_Wallet__","message":"Don't go around the lock with the renamed attribute: use the property `money` and `super().spend(...)`."}]},{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Robot:\n    def __init__(self):\n        self.__code = \"R1\"\n\n    def robot_code(self):\n        return self.__code\n\n\nclass Drone(Robot):\n    def __init__(self):\n        super().__init__()\n        self.__code = \"D7\"\n\n    def drone_code(self):\n        return self.__code\n\n\nbee = Drone()\nprint(bee.drone_code())\nprint(bee.robot_code())\n","solution":"class Robot:\n    def __init__(self):\n        self.__code = \"R1\"\n\n    def robot_code(self):\n        return self.__code\n\n\nclass Drone(Robot):\n    def __init__(self):\n        super().__init__()\n        self.__code = \"D7\"\n\n    def drone_code(self):\n        return self.__code\n\n\nbee = Drone()\nprint(bee.drone_code())\nprint(bee.robot_code())\n","hints":["Python renames a private attribute after the class it's written in. In `Robot`, `self.__code` is really `self._Robot__code`.","In `Drone`, `self.__code` is `self._Drone__code`: a different attribute. So the drone has two codes, and each class's methods see their own."],"tests":[{"name":"Output","stdin":"","expect":"D7\nR1","hidden":false}],"require":[],"forbid":[],"lines":["D7","R1"]}]}