An object as an attribute
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:
class Author:
def __init__(self, name, country):
self.name = name
self.country = country
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
writer = Author("Ana Lind", "Sweden")
novel = Book("The Quiet Sea", writer)
poems = Book("Salt and Stone", writer)
print(novel.title)
print(novel.author.name)
print(poems.author.country)
The Quiet Sea
Ana Lind
Swedennovel.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:
who = novel.author
print(who.name)
The 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.
The outer object uses the inner one
The 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__:
class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
self.running = False
def start(self):
self.running = True
def __str__(self):
state = "running" if self.running else "off"
return f"{self.horsepower} hp, {state}"
class Car:
def __init__(self, model, engine):
self.model = model
self.engine = engine
def start(self):
self.engine.start()
print(f"{self.model}: vroom")
def __str__(self):
return f"{self.model} (engine: {self.engine})"
car = Car("Roadster", Engine(150))
print(car)
car.start()
print(car)
Roadster (engine: 150 hp, off)
Roadster: vroom
Roadster (engine: 150 hp, running)The 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.