Skip to content

Classes and objects

~20 min ยท prerequisite: Parts 1 to 4

What you will be able to do

  • Define a class with an __init__ method that sets up each new object.
  • Read and change an object's attributes, and call its methods.
  • Explain what self refers to inside a method, and check whether a value belongs to a class with isinstance.

The idea

So far, you have stored each value in its own variable and passed values to functions. When a program tracks several facts about one thing, such as a product's name, price, and quantity, loose variables are hard to keep together. A class groups related data and the functions that work on that data under one name.

Think of a class as a blueprint and an object as one thing built from it. A house blueprint is not a house. You can build many houses from one blueprint, and each house can have its own paint color. In Python, Product is the blueprint, and each product you create is an instance of Product. You define a class with the keyword class, followed by a name in CapWords style, such as Product. The indented block under it holds methods. A method is a function defined inside a class. The method named __init__ runs automatically each time you create an object. Its job is to set up the new object's attributes, which are values stored on the object and written as self.name.

The first parameter of every method is self. Python passes the object into it for you. When you write item.total_value(), Python calls the method with item as its first argument. Inside the method, self refers to item, so self.price reads that object's own price. Two products have separate attributes, so changing one does not change the other.

Calling a class looks like calling a function. The expression Product("Pen", 2.5, 10) creates a new object, runs __init__, and returns the object.

Every object knows its class. Use isinstance(item, Product) to check whether item belongs to Product.

Worked example

class Product:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

    def total_value(self):
        return self.price * self.quantity

    def restock(self, amount):
        self.quantity += amount


pen = Product("Pen", 2.5, 10)
mug = Product("Mug", 8.0, 3)

print(pen.total_value())
pen.restock(5)
print(pen.quantity)
print(mug.total_value())

The output is 25.0, then 15, then 24.0. Line by line, this is what Python does:

  1. class Product: defines a new class named Product. The colon starts an indented block.
  2. def __init__(self, name, price, quantity): defines the initializer. Python calls it with the new object as self, plus the three values you pass in.
  3. The next three lines store each value as an attribute on self. After them, the object carries its own name, price, and quantity.
  4. total_value multiplies its object's price by its quantity and returns the result.
  5. restock adds amount to the quantity of the object it was called on.
  6. Product("Pen", 2.5, 10) creates the first object, and Product("Mug", 8.0, 3) creates a second, separate one.
  7. pen.total_value() computes 2.5 * 10, which is 25.0.
  8. pen.restock(5) changes only pen, so its quantity becomes 15.
  9. mug.total_value() computes 8.0 * 3, which is 24.0. The change to pen did not touch mug.

Try it

The first block checks one object. The second shows that objects keep separate state.

class Product:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

    def total_value(self):
        return self.price * self.quantity


pen = Product("Pen", 2.5, 10)
assert pen.name == "Pen"
assert pen.total_value() == 25.0
assert type(pen) is Product
assert isinstance(pen, Product)
class Counter:
    def __init__(self):
        self.count = 0

    def add(self):
        self.count += 1


first = Counter()
second = Counter()
first.add()
first.add()
second.add()
assert first.count == 2
assert second.count == 1
assert Counter().count == 0

Common mistakes

  1. Leaving out self in a method. Writing def total_value(): and then calling pen.total_value() gives:

    TypeError: Product.total_value() takes 0 positional arguments but 1 was given
    

    Add self as the first parameter of every method.

  2. Forgetting self. when storing an attribute. Writing name = name inside __init__ only changes a local variable. Then pen.name gives:

    AttributeError: 'Product' object has no attribute 'name'
    

    Write self.name = name instead.

  3. Leaving out an argument when you create an object. Writing Product("Pen", 2.5) gives:

    TypeError: Product.__init__() missing 1 required positional argument: 'quantity'
    
  4. Forgetting the parentheses on a method. Writing assert pen.total_value == 25.0 does not call the method. It compares a method object with a number, so the check fails:

    AssertionError
    

    Write pen.total_value() to call it.

  5. Reading an attribute that was never set. Writing pen.color when __init__ never sets color gives:

    AttributeError: 'Product' object has no attribute 'color'
    

Practice

  1. Create a class Book whose __init__ takes title and pages. Create a book titled "Dune" with 412 pages, and assert both attributes.
  2. Add a method pages_left(self, read) to Book that returns the number of pages not yet read. Assert that Book("Dune", 412).pages_left(100) is 312.
  3. Create a class Counter with a value attribute that starts at 0, and a method increment(self, step=1) that adds step to value. Call increment() once and increment(5) once, then assert value is 6.
  4. Create two Book objects. Change the title of one, then assert the other one keeps its original title.
  5. Stretch: create a class Rectangle with width and height, a method area(), and a method can_fit(self, other) that returns True when other fits inside self, with width and height each no larger. Assert Rectangle(4, 5).can_fit(Rectangle(3, 5)) is True and Rectangle(4, 5).can_fit(Rectangle(5, 1)) is False.
Solution
  1. ```python class Book: def init(self, title, pages): self.title = title self.pages = pages

book = Book("Dune", 412) assert book.title == "Dune" assert book.pages == 412 ```

  1. ```python class Book: def init(self, title, pages): self.title = title self.pages = pages

    def pages_left(self, read): return self.pages - read

assert Book("Dune", 412).pages_left(100) == 312 ```

  1. ```python class Counter: def init(self): self.value = 0

    def increment(self, step=1): self.value += step

counter = Counter() counter.increment() counter.increment(5) assert counter.value == 6 ```

  1. ```python class Book: def init(self, title, pages): self.title = title self.pages = pages

first = Book("Dune", 412) second = Book("Emma", 474) first.title = "Dune Messiah" assert second.title == "Emma" ```

  1. ```python class Rectangle: def init(self, width, height): self.width = width self.height = height

    def area(self): return self.width * self.height

    def can_fit(self, other): return other.width <= self.width and other.height <= self.height

assert Rectangle(4, 5).can_fit(Rectangle(3, 5)) is True assert Rectangle(4, 5).can_fit(Rectangle(5, 1)) is False ```

Recap

  • A class is a blueprint, and each object made from it has its own attributes. __init__ sets those attributes when the object is created.
  • Every method takes self as its first parameter, and self refers to the object the method was called on.
  • Use isinstance to check whether an object belongs to a class.

Sources