{"id":"object-references-arguments","slug":"objects-as-arguments","title":"Objects as arguments","text":"### A function can change the object it gets\n\nYou have written functions that change an object they get. Now you can see why that works: when you call a function with an object, the parameter refers to the same object as the argument. Nothing is copied. So when the function changes the object's attributes, the caller sees the change after the call, just as with lists in the References module:\n\n```python\nclass Hero:\n    def __init__(self, name):\n        self.name = name\n        self.level = 1\n\n    def __str__(self):\n        return f\"{self.name} (level {self.level})\"\n\n\ndef level_up(hero):\n    hero.level += 1\n\n\nayla = Hero(\"Ayla\")\nprint(ayla)\nlevel_up(ayla)\nlevel_up(ayla)\nprint(ayla)\n```\n\n```output\nAyla (level 1)\nAyla (level 3)\n```\n\nDuring the call, `hero` inside the function and `ayla` outside it are two names for one object.\n\nThere is one catch. Giving the parameter a new value inside the function, such as `hero = Hero(\"Bram\")`, only makes the local name refer to another object. The caller's variable still refers to the old one. To change the caller's object, change its attributes.\n\n### A function can create an object\n\nA function can also create a new object and return it. The caller gets a reference to it, and can keep it in a variable or a list. This function turns a line of text into a `Hero`:\n\n```python\nclass Hero:\n    def __init__(self, name):\n        self.name = name\n        self.level = 1\n\n    def __str__(self):\n        return f\"{self.name} (level {self.level})\"\n\n\ndef hero_from_text(line):\n    parts = line.split(\";\")\n    hero = Hero(parts[0])\n    hero.level = int(parts[1])\n    return hero\n\n\nteam = []\nfor line in [\"Ayla;3\", \"Bram;5\"]:\n    team.append(hero_from_text(line))\n\nfor hero in team:\n    print(hero)\n```\n\n```output\nAyla (level 3)\nBram (level 5)\n```\n\nThe object created inside the function lives on after the function ends, because the caller still has a reference to it.\n\n### A method can take an object too\n\nMethods are functions that belong to a class, so they can take objects as arguments as well. A method that gets an object of another class can read and change that object, and its own object at the same time. Here a coach trains athletes: training makes the athlete faster, and the coach counts the sessions.\n\n```python\nclass Athlete:\n    def __init__(self, name, speed):\n        self.name = name\n        self.speed = speed\n\n\nclass Coach:\n    def __init__(self, name, boost):\n        self.name = name\n        self.boost = boost\n        self.sessions = 0\n\n    def train(self, athlete):\n        athlete.speed += self.boost\n        self.sessions += 1\n        print(f\"{self.name} trained {athlete.name}, speed now {athlete.speed}\")\n\n\nmia = Athlete(\"Mia\", 20)\ntom = Athlete(\"Tom\", 25)\nray = Coach(\"Coach Ray\", 3)\nray.train(mia)\nray.train(mia)\nray.train(tom)\nprint(\"Sessions:\", ray.sessions)\n```\n\n```output\nCoach Ray trained Mia, speed now 23\nCoach Ray trained Mia, speed now 26\nCoach Ray trained Tom, speed now 28\nSessions: 3\n```\n\nInside `train`, `self` is the coach the method is called on (the object before the dot), and `athlete` is the athlete in the parentheses. One call, `ray.train(mia)`, changes both objects: Mia's speed and Ray's count of sessions.\n","task":"A library lends books. Fill in the blanks in the method `lend`, so that it marks the book as lent and counts the loans of the library. The program should print:\n\n```\nWorld Atlas lent out\nWorld Atlas is already on loan\nCookbook lent out\nLoans: 2\n```\n","kind":"fill","seed":"class Book:\n    def __init__(self, title):\n        self.title = title\n        self.on_loan = False\n\n\nclass Library:\n    def __init__(self):\n        self.loans = 0\n\n    def lend(self, book):\n        if book.on_loan:\n            print(f\"{book.title} is already on loan\")\n        else:\n            [[book]].on_loan = True\n            [[self]].loans += 1\n            print(f\"{book.title} lent out\")\n\n\nlibrary = Library()\natlas = Book(\"World Atlas\")\nlibrary.lend(atlas)\nlibrary.lend(atlas)\nlibrary.lend(Book(\"Cookbook\"))\nprint(\"Loans:\", library.loans)\n","solution":"class Book:\n    def __init__(self, title):\n        self.title = title\n        self.on_loan = False\n\n\nclass Library:\n    def __init__(self):\n        self.loans = 0\n\n    def lend(self, book):\n        if book.on_loan:\n            print(f\"{book.title} is already on loan\")\n        else:\n            book.on_loan = True\n            self.loans += 1\n            print(f\"{book.title} lent out\")\n\n\nlibrary = Library()\natlas = Book(\"World Atlas\")\nlibrary.lend(atlas)\nlibrary.lend(atlas)\nlibrary.lend(Book(\"Cookbook\"))\nprint(\"Loans:\", library.loans)\n","hints":["Inside `lend` there are two objects: the library the method is called on, and the book in the parentheses. What are their names inside the method?","The book is the parameter `book`, and the library is `self`. `on_loan` belongs to the book, `loans` to the library."],"tests":[{"name":"Output","stdin":"","expect":"World Atlas lent out\nWorld Atlas is already on loan\nCookbook lent out\nLoans: 2","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Write a function `parse_book(line)`. It gets a line of text with a book's title, author and number of pages, separated by semicolons, such as `\"The Quiet Sea;Ana Lind;318\"`, and returns a new `Book` object made from it. The number of pages must be a number (an `int`), not text.\n\nFor example, this code:\n\n```python\nbook = parse_book(\"The Quiet Sea;Ana Lind;318\")\nprint(book)\nprint(book.pages + 1)\n```\n\nprints:\n\n```\nThe Quiet Sea by Ana Lind, 318 pages\n319\n```\n","kind":"code","seed":"class Book:\n    def __init__(self, title, author, pages):\n        self.title = title\n        self.author = author\n        self.pages = pages\n\n    def __str__(self):\n        return f\"{self.title} by {self.author}, {self.pages} pages\"\n\n\ndef parse_book(line):\n    pass\n\n\nif __name__ == \"__main__\":\n    book = parse_book(\"The Quiet Sea;Ana Lind;318\")\n    print(book)\n    print(book.pages + 1)\n","solution":"class Book:\n    def __init__(self, title, author, pages):\n        self.title = title\n        self.author = author\n        self.pages = pages\n\n    def __str__(self):\n        return f\"{self.title} by {self.author}, {self.pages} pages\"\n\n\ndef parse_book(line):\n    parts = line.split(\";\")\n    return Book(parts[0], parts[1], int(parts[2]))\n\n\nif __name__ == \"__main__\":\n    book = parse_book(\"The Quiet Sea;Ana Lind;318\")\n    print(book)\n    print(book.pages + 1)\n","hints":["`line.split(\";\")` gives a list of three strings: the title, the author and the pages.","The pages are still text after splitting. Turn them into a number with `int(...)`.","Create the object with `Book(parts[0], parts[1], int(parts[2]))` and return it."],"tests":[{"name":"Test 1","stdin":"","call":"book = parse_book(\"The Quiet Sea;Ana Lind;318\")\nprint(book)\nprint(book.pages + 1)\n","expect":"The Quiet Sea by Ana Lind, 318 pages\n319","hidden":false},{"name":"Test 2","stdin":"","call":"shelf = []\nfor line in [\"Small Gods;Lee Park;290\", \"Rain Atlas;Omar Diaz;145\"]:\n    shelf.append(parse_book(line))\ntotal = 0\nfor book in shelf:\n    print(book)\n    total += book.pages\nprint(total)\n","expect":"Small Gods by Lee Park, 290 pages\nRain Atlas by Omar Diaz, 145 pages\n435","hidden":true},{"name":"Test 3","stdin":"","call":"book = parse_book(\"A;B;7\")\nprint(book.title, book.author, book.pages * 2)\n","expect":"A B 14","hidden":true}],"require":[],"forbid":[]},{"task":"A ferry has room for cars up to a total length in meters. Write the method `board(self, car)` in the class `Ferry`:\n\n- If the car fits in the space that's left, it boards: the ferry's `space_left` goes down by the car's `length`, its `cars` goes up by one, and the method returns `True`.\n- If the car doesn't fit, nothing changes and the method returns `False`.\n\nFor example, this code:\n\n```python\nferry = Ferry(10)\nprint(ferry.board(Car(\"ABC-123\", 4)))\nprint(ferry.board(Car(\"XYZ-789\", 7)))\nprint(ferry)\n```\n\nprints:\n\n```\nTrue\nFalse\ncars: 1, space left: 6 m\n```\n","kind":"code","seed":"class Car:\n    def __init__(self, plate, length):\n        self.plate = plate\n        self.length = length\n\n\nclass Ferry:\n    def __init__(self, space):\n        self.space_left = space\n        self.cars = 0\n\n    def board(self, car):\n        pass\n\n    def __str__(self):\n        return f\"cars: {self.cars}, space left: {self.space_left} m\"\n\n\nif __name__ == \"__main__\":\n    ferry = Ferry(10)\n    print(ferry.board(Car(\"ABC-123\", 4)))\n    print(ferry.board(Car(\"XYZ-789\", 7)))\n    print(ferry)\n","solution":"class Car:\n    def __init__(self, plate, length):\n        self.plate = plate\n        self.length = length\n\n\nclass Ferry:\n    def __init__(self, space):\n        self.space_left = space\n        self.cars = 0\n\n    def board(self, car):\n        if car.length > self.space_left:\n            return False\n        self.space_left -= car.length\n        self.cars += 1\n        return True\n\n    def __str__(self):\n        return f\"cars: {self.cars}, space left: {self.space_left} m\"\n\n\nif __name__ == \"__main__\":\n    ferry = Ferry(10)\n    print(ferry.board(Car(\"ABC-123\", 4)))\n    print(ferry.board(Car(\"XYZ-789\", 7)))\n    print(ferry)\n","hints":["Inside `board`, the car's length is `car.length` and the space the ferry has left is `self.space_left`.","First check whether the car is too long for the space that's left. If it is, return `False` right away.","Otherwise change the ferry's two attributes (`self.space_left -= car.length` and `self.cars += 1`) and return `True`."],"tests":[{"name":"Test 1","stdin":"","call":"ferry = Ferry(10)\nprint(ferry.board(Car(\"ABC-123\", 4)))\nprint(ferry.board(Car(\"XYZ-789\", 7)))\nprint(ferry)\n","expect":"True\nFalse\ncars: 1, space left: 6 m","hidden":false},{"name":"Test 2","stdin":"","call":"ferry = Ferry(12)\nprint(ferry.board(Car(\"A-1\", 5)))\nprint(ferry.board(Car(\"B-2\", 7)))\nprint(ferry.board(Car(\"C-3\", 1)))\nprint(ferry)\n","expect":"True\nTrue\nFalse\ncars: 2, space left: 0 m","hidden":true},{"name":"Test 3","stdin":"","call":"ferry = Ferry(20)\nfor length in [6, 9, 8, 4, 1]:\n    print(ferry.board(Car(\"X\", length)))\nprint(ferry)\n","expect":"True\nTrue\nFalse\nTrue\nTrue\ncars: 4, space left: 0 m","hidden":true}],"require":[],"forbid":[]}]}