{"id":"methods-first","slug":"first-methods","title":"Methods in a class","text":"In the previous module, a `Room` worked out its area in the constructor and kept it in an attribute. That number is right when the room is made, and wrong as soon as the room changes:\n\n```python\nclass Room:\n    def __init__(self, name, width, length):\n        self.name = name\n        self.width = width\n        self.length = length\n        self.area = width * length\n\n\nkitchen = Room(\"Kitchen\", 3, 4)\nkitchen.width = 5\nprint(kitchen.width, kitchen.length, kitchen.area)\n```\n\n```output\n5 4 12\n```\n\nA room 5 meters by 4 isn't 12 square meters. The area shouldn't be stored at all: it should be worked out when someone asks for it, from the width and the length the room has at that moment. That's a job for a **method**, a function that is part of the class and works with the attributes of the object it is called on.\n\nA method is defined inside the class, as the constructor is, and its first parameter is `self`, the object it is called on:\n\n```python\nclass Room:\n    def __init__(self, name, width, length):\n        self.name = name\n        self.width = width\n        self.length = length\n\n    def area(self):\n        return self.width * self.length\n\n    def describe(self):\n        return f\"{self.name}: {self.area()} square meters\"\n\n\nkitchen = Room(\"Kitchen\", 3, 4)\nprint(kitchen.area())\nkitchen.width = 5\nprint(kitchen.area())\nprint(kitchen.describe())\n```\n\n```output\n12\n20\nKitchen: 20 square meters\n```\n\n- The methods are indented inside the class, with one blank line between them.\n- When you call `kitchen.area()`, Python passes `kitchen` to the method as `self`, so `self.width` inside the method is the kitchen's width.\n- `area` works out the area from the attributes as they are when it is called. So, unlike an area stored in the constructor, it is never out of date: after the width changes, the next call gives the new area.\n- A method can call another method of the same object through `self`: `describe` calls `self.area()`.\n\n### Methods with parameters\n\nA method can take arguments after `self`, as any function can:\n\n```python\nclass Product:\n    def __init__(self, name, price):\n        self.name = name\n        self.price = price\n\n    def sale_price(self, percent):\n        return self.price * (100 - percent) / 100\n\n\nlamp = Product(\"Desk lamp\", 40)\nprint(lamp.sale_price(25))\nprint(lamp.sale_price(10))\n```\n\n```output\n30.0\n36.0\n```\n\n`lamp.sale_price(25)` has one argument in the parentheses, and inside the method `self` is the lamp and `percent` is 25.\n\n### Forgetting self\n\nEvery method needs `self` as its first parameter. Leave it out, and the call fails, because Python always passes the object:\n\n```python\nclass Room:\n    def __init__(self, width, length):\n        self.width = width\n        self.length = length\n\n    def area():\n        return self.width * self.length\n\n\nhall = Room(2, 6)\nprint(hall.area())\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 11, in <module>\n    print(hall.area())\n          ~~~~~~~~~^^\nTypeError: Room.area() takes 0 positional arguments but 1 was given\n```\n\nThe \"1 given\" is the object itself. Inside a method, the attributes also need `self.` in front: a plain `width` is a `NameError`, and Python's message asks \"Did you mean: 'self.width'?\"\n","task":"Fill in the blanks, so that the class has a method `diameter` and the program prints `60`.\n","kind":"fill","seed":"class Circle:\n    def __init__(self, radius):\n        self.radius = radius\n\n    def diameter([[self]]):\n        return 2 * [[self.radius]]\n\n\nwheel = Circle(30)\nprint(wheel.[[diameter()]])\n","solution":"class Circle:\n    def __init__(self, radius):\n        self.radius = radius\n\n    def diameter(self):\n        return 2 * self.radius\n\n\nwheel = Circle(30)\nprint(wheel.diameter())\n","hints":["Every method has the object itself as its first parameter.","Inside the method, the radius is the object's attribute: `self.radius`.","To call the method, write its name with parentheses after the dot: `wheel.diameter()`."],"tests":[{"name":"Output","stdin":"","expect":"60","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Write a class `Temperature` whose constructor takes a temperature in degrees Celsius and stores it in the attribute `celsius`. Give it two methods:\n\n- `fahrenheit()` returns the temperature in degrees Fahrenheit: the Celsius degrees times 9, divided by 5, plus 32.\n- `is_freezing()` returns `True` when the temperature is 0 degrees Celsius or less, and `False` otherwise.\n\nThis code:\n\n```python\nnoon = Temperature(25)\nnight = Temperature(-10)\nprint(noon.fahrenheit(), noon.is_freezing())\nprint(night.fahrenheit(), night.is_freezing())\n```\n\nprints\n\n```\n77.0 False\n14.0 True\n```\n","kind":"code","seed":"class Temperature:\n    def __init__(self, celsius):\n        self.celsius = celsius\n\n    # Write the methods here.\n\n\nif __name__ == \"__main__\":\n    noon = Temperature(25)\n    print(noon.fahrenheit(), noon.is_freezing())\n","solution":"class Temperature:\n    def __init__(self, celsius):\n        self.celsius = celsius\n\n    def fahrenheit(self):\n        return self.celsius * 9 / 5 + 32\n\n    def is_freezing(self):\n        return self.celsius <= 0\n\n\nif __name__ == \"__main__\":\n    noon = Temperature(25)\n    print(noon.fahrenheit(), noon.is_freezing())\n","hints":["Each method starts like `def fahrenheit(self):`, indented inside the class at the same level as `__init__`.","Use the attribute in the calculation: `return self.celsius * 9 / 5 + 32`.","A comparison is already `True` or `False`, so `is_freezing` can return it: `return self.celsius <= 0`."],"tests":[{"name":"Test 1","stdin":"","call":"noon = Temperature(25)\nnight = Temperature(-10)\nprint(noon.fahrenheit(), noon.is_freezing())\nprint(night.fahrenheit(), night.is_freezing())\n","expect":"77.0 False\n14.0 True","hidden":false},{"name":"Test 2","stdin":"","call":"t = Temperature(0)\nprint(t.fahrenheit(), t.is_freezing())\nt.celsius = 100\nprint(t.fahrenheit(), t.is_freezing())\n","expect":"32.0 True\n212.0 False","hidden":true},{"name":"Temperature(36).fahrenheit()","stdin":"","call":"Temperature(36).fahrenheit()\n","expect":"96.8","hidden":true,"returns":true}],"require":[],"forbid":[]},{"task":"Write a class `Password` whose constructor stores the password in the attribute `text`. Give it three methods:\n\n- `length()` returns the number of characters in the password.\n- `has_digit()` returns `True` when the password has at least one digit (0 to 9) in it.\n- `is_strong()` returns `True` when the password is at least 8 characters long and has a digit. Use the other two methods to find that out.\n\nThis code:\n\n```python\nweak = Password(\"sunshine\")\nstrong = Password(\"tr4in-ride\")\nprint(weak.length(), weak.has_digit(), weak.is_strong())\nprint(strong.length(), strong.has_digit(), strong.is_strong())\n```\n\nprints\n\n```\n8 False False\n10 True True\n```\n","kind":"code","seed":"class Password:\n    def __init__(self, text):\n        self.text = text\n\n    # Write the methods here.\n\n\nif __name__ == \"__main__\":\n    weak = Password(\"sunshine\")\n    print(weak.length(), weak.has_digit(), weak.is_strong())\n","solution":"class Password:\n    def __init__(self, text):\n        self.text = text\n\n    def length(self):\n        return len(self.text)\n\n    def has_digit(self):\n        for char in self.text:\n            if char in \"0123456789\":\n                return True\n        return False\n\n    def is_strong(self):\n        return self.length() >= 8 and self.has_digit()\n\n\nif __name__ == \"__main__\":\n    weak = Password(\"sunshine\")\n    print(weak.length(), weak.has_digit(), weak.is_strong())\n","hints":["`length` returns `len(self.text)`.","In `has_digit`, go through the characters with `for char in self.text:` and return `True` as soon as one is `in \"0123456789\"`. After the loop, return `False`.","`is_strong` calls the other methods through `self`: `self.length() >= 8 and self.has_digit()`."],"tests":[{"name":"Test 1","stdin":"","call":"weak = Password(\"sunshine\")\nstrong = Password(\"tr4in-ride\")\nprint(weak.length(), weak.has_digit(), weak.is_strong())\nprint(strong.length(), strong.has_digit(), strong.is_strong())\n","expect":"8 False False\n10 True True","hidden":false},{"name":"Test 2","stdin":"","call":"for text in [\"abc1\", \"12345678\", \"\", \"longpasswordbutnodigits\", \"9lives-of-a-cat\"]:\n    p = Password(text)\n    print(p.length(), p.has_digit(), p.is_strong())\n","expect":"4 True False\n8 True True\n0 False False\n23 False False\n15 True True","hidden":true}],"require":[{"pattern":"self\\.has_digit\\(\\)","message":"Use the method `has_digit` in `is_strong`: call it with `self.has_digit()`."}],"forbid":[]}]}