{"id":"objects-as-attributes-object","slug":"object-in-object","title":"An object as an attribute","text":"Things are often made of other things, or belong to them: a book has an author, an order has a customer, a car has an engine. A program models that with an attribute whose value is another object, made from a class of your own:\n\n```python\nclass Author:\n    def __init__(self, name, country):\n        self.name = name\n        self.country = country\n\n\nclass Book:\n    def __init__(self, title, author):\n        self.title = title\n        self.author = author\n\n\nwriter = Author(\"Ana Lind\", \"Sweden\")\nnovel = Book(\"The Quiet Sea\", writer)\npoems = Book(\"Salt and Stone\", writer)\n\nprint(novel.title)\nprint(novel.author.name)\nprint(poems.author.country)\n```\n\n```output\nThe Quiet Sea\nAna Lind\nSweden\n```\n\n`novel.author.name` takes two hops. `novel.author` gives the `Author` object stored in the book, and `.name` then reads that author's name. It means the same as these two lines, with a variable in between:\n\n```python\nwho = novel.author\nprint(who.name)\n```\n\nThe attribute holds a reference, as every variable does. Both books refer to the same `Author` object, so if the author changed her name with `writer.name = \"Ana Berg\"`, both books would show the new name at once.\n\n### The outer object uses the inner one\n\nThe methods of the outer object can use the inner object's attributes and call its methods. And when an f-string has an object in its braces, Python uses that object's own `__str__`:\n\n```python\nclass Engine:\n    def __init__(self, horsepower):\n        self.horsepower = horsepower\n        self.running = False\n\n    def start(self):\n        self.running = True\n\n    def __str__(self):\n        state = \"running\" if self.running else \"off\"\n        return f\"{self.horsepower} hp, {state}\"\n\n\nclass Car:\n    def __init__(self, model, engine):\n        self.model = model\n        self.engine = engine\n\n    def start(self):\n        self.engine.start()\n        print(f\"{self.model}: vroom\")\n\n    def __str__(self):\n        return f\"{self.model} (engine: {self.engine})\"\n\n\ncar = Car(\"Roadster\", Engine(150))\nprint(car)\ncar.start()\nprint(car)\n```\n\n```output\nRoadster (engine: 150 hp, off)\nRoadster: vroom\nRoadster (engine: 150 hp, running)\n```\n\nThe car doesn't know how to start an engine; `self.engine.start()` asks the engine to do it. Each class looks after its own attributes, and the car only talks to its engine through the engine's methods. The engine object is created right in the call, `Car(\"Roadster\", Engine(150))`: it doesn't need a variable of its own, because the car keeps a reference to it.\n","task":"Fill in the blanks so the program prints the customer's shipping label:\n\n```\nLea Park\n12 Elm Street\nSpringfield\n```\n","kind":"fill","seed":"class Address:\n    def __init__(self, street, city):\n        self.street = street\n        self.city = city\n\n\nclass Customer:\n    def __init__(self, name, address):\n        self.name = name\n        self.address = address\n\n\nhome = Address(\"12 Elm Street\", \"Springfield\")\ncustomer = Customer(\"Lea Park\", home)\nprint(customer.name)\nprint(customer.[[address]].street)\nprint(customer.address.[[city]])\n","solution":"class Address:\n    def __init__(self, street, city):\n        self.street = street\n        self.city = city\n\n\nclass Customer:\n    def __init__(self, name, address):\n        self.name = name\n        self.address = address\n\n\nhome = Address(\"12 Elm Street\", \"Springfield\")\ncustomer = Customer(\"Lea Park\", home)\nprint(customer.name)\nprint(customer.address.street)\nprint(customer.address.city)\n","hints":["The customer's address is in the attribute `address`, and it's an `Address` object.","An `Address` has the attributes `street` and `city`. `customer.address.city` goes from the customer to its address, and from there to the city."],"tests":[{"name":"Output","stdin":"","expect":"Lea Park\n12 Elm Street\nSpringfield","hidden":false}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Coach:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Team:\n    def __init__(self, name, coach):\n        self.name = name\n        self.coach = coach\n\n\nlee = Coach(\"Lee\")\nreds = Team(\"Reds\", lee)\nblues = Team(\"Blues\", lee)\nblues.coach.name = \"Kim\"\nprint(reds.coach.name)\nprint(blues.name, blues.coach.name)\n","solution":"class Coach:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Team:\n    def __init__(self, name, coach):\n        self.name = name\n        self.coach = coach\n\n\nlee = Coach(\"Lee\")\nreds = Team(\"Reds\", lee)\nblues = Team(\"Blues\", lee)\nblues.coach.name = \"Kim\"\nprint(reds.coach.name)\nprint(blues.name, blues.coach.name)\n","hints":["`Coach(...)` is called only once, so both teams have the same coach object.","`blues.coach.name = \"Kim\"` renames that one coach, so the Reds see the change too."],"tests":[{"name":"Output","stdin":"","expect":"Kim\nBlues Kim","hidden":false}],"require":[],"forbid":[],"lines":["Kim","Blues Kim"]},{"task":"An `Airport` has a code and a city, and a `Flight` has a flight number and two airports, where it leaves from (`origin`) and where it lands (`destination`). Write two methods in the class `Flight`:\n\n- `__str__(self)` returns a string such as `AY 123: Helsinki (HEL) to Oslo (OSL)`, using the airports' own `__str__`.\n- `return_flight(self, number)` returns a new `Flight` object with the given number that flies the same route the other way around.\n\nFor example, this code:\n\n```python\nhel = Airport(\"HEL\", \"Helsinki\")\nosl = Airport(\"OSL\", \"Oslo\")\nthere = Flight(\"AY 123\", hel, osl)\nback = there.return_flight(\"AY 124\")\nprint(there)\nprint(back)\n```\n\nprints:\n\n```\nAY 123: Helsinki (HEL) to Oslo (OSL)\nAY 124: Oslo (OSL) to Helsinki (HEL)\n```\n","kind":"code","seed":"class Airport:\n    def __init__(self, code, city):\n        self.code = code\n        self.city = city\n\n    def __str__(self):\n        return f\"{self.city} ({self.code})\"\n\n\nclass Flight:\n    def __init__(self, number, origin, destination):\n        self.number = number\n        self.origin = origin\n        self.destination = destination\n\n\nif __name__ == \"__main__\":\n    hel = Airport(\"HEL\", \"Helsinki\")\n    osl = Airport(\"OSL\", \"Oslo\")\n    there = Flight(\"AY 123\", hel, osl)\n    back = there.return_flight(\"AY 124\")\n    print(there)\n    print(back)\n","solution":"class Airport:\n    def __init__(self, code, city):\n        self.code = code\n        self.city = city\n\n    def __str__(self):\n        return f\"{self.city} ({self.code})\"\n\n\nclass Flight:\n    def __init__(self, number, origin, destination):\n        self.number = number\n        self.origin = origin\n        self.destination = destination\n\n    def __str__(self):\n        return f\"{self.number}: {self.origin} to {self.destination}\"\n\n    def return_flight(self, number):\n        return Flight(number, self.destination, self.origin)\n\n\nif __name__ == \"__main__\":\n    hel = Airport(\"HEL\", \"Helsinki\")\n    osl = Airport(\"OSL\", \"Oslo\")\n    there = Flight(\"AY 123\", hel, osl)\n    back = there.return_flight(\"AY 124\")\n    print(there)\n    print(back)\n","hints":["In an f-string, `{self.origin}` uses the `Airport` class's `__str__`, so it becomes `Helsinki (HEL)`.","`__str__` can be one line: `return f\"{self.number}: {self.origin} to {self.destination}\"`.","`return_flight` creates a new flight with the two airports swapped: `Flight(number, self.destination, self.origin)`."],"tests":[{"name":"Test 1","stdin":"","call":"hel = Airport(\"HEL\", \"Helsinki\")\nosl = Airport(\"OSL\", \"Oslo\")\nthere = Flight(\"AY 123\", hel, osl)\nback = there.return_flight(\"AY 124\")\nprint(there)\nprint(back)\n","expect":"AY 123: Helsinki (HEL) to Oslo (OSL)\nAY 124: Oslo (OSL) to Helsinki (HEL)","hidden":false},{"name":"Test 2","stdin":"","call":"rome = Airport(\"FCO\", \"Rome\")\nlima = Airport(\"LIM\", \"Lima\")\nflight = Flight(\"XQ 7\", lima, rome)\nprint(flight)\nback = flight.return_flight(\"XQ 8\")\nprint(back.origin.city, back.destination.code)\nprint(flight)\n","expect":"XQ 7: Lima (LIM) to Rome (FCO)\nRome LIM\nXQ 7: Lima (LIM) to Rome (FCO)","hidden":true}],"require":[],"forbid":[]}]}