Python Quest

Track 11 · Lesson 1 of 12

Classes and objects

Why classes exist, how a class differs from the objects made from it, and how objects carry their own attributes.

About 30 minBeginner4 exercisesFree

You will be able to

  • Explain what problem classes solve compared with loose variables and dictionaries
  • Tell the difference between a class and an instance (object) of it
  • Define a class and create several objects from it
  • Read and set attributes on an object with dot notation

A courier company tracks parcels. So far each parcel is a dictionary: {"tracking": "QX-1042", "weight_kg": 2.5}. That works until the program grows. One function spells the key "weight", another "weight_kg". Somebody builds a parcel without a tracking code. Every function that handles parcels has to remember which keys exist.

A class lets you define your own type — Parcel — that says once what every parcel looks like. Each parcel you create from it is an object (also called an instance) that carries its own data. You have used objects all along: "hello" is an object of class str, [1, 2] is an object of class list. In this track you write classes of your own.

Objects of built-in classes
word = "parcel"
prices = [12.5, 30]

print(type(word))
print(type(prices))
print(word.upper())      # a method that belongs to str objects
print(len(prices))

Try it: Add print(type(3.5)) and print(type(True)). Every value in Python is an object of some class.

Defining a class

A class definition starts with the keyword class, a name in CapitalisedWords style, and a colon. The indented body describes the class. The smallest possible class has an empty body, written with pass:

class Parcel:
    pass

Calling the class like a function — Parcel() — creates a new object. You can then attach attributes (named values stored on the object) with dot notation: p.weight_kg = 2.5. Reading p.weight_kg gives the value back.

A class is a blueprint; each call creates a separate object
class Parcel:
    pass

first = Parcel()
first.tracking = "QX-1042"
first.weight_kg = 2.5

second = Parcel()
second.tracking = "QX-1043"
second.weight_kg = 0.8

print(first.tracking, first.weight_kg)
print(second.tracking, second.weight_kg)
print(type(first).__name__)
print(first is second)

Try it: Change second.weight_kg to 4.0 and print first.weight_kg again. Changing one object does not touch the other.

The class Parcel is the blueprint; first and second are two instances built from it. Each instance has its own attributes, so changing second.weight_kg leaves first alone. first is second is False because they are two different objects.

Setting attributes when the object is created

Attaching attributes one by one after creation is fragile: forget one line and that parcel has no weight. Instead, a class can define a special function named __init__ (two underscores on each side). Python runs it automatically every time you create an object, passing the new object in as the first parameter, conventionally called self, followed by the arguments you gave the class:

__init__ sets up every new object the same way
class Parcel:
    def __init__(self, tracking, weight_kg):
        self.tracking = tracking
        self.weight_kg = weight_kg

a = Parcel("QX-1042", 2.5)
b = Parcel("QX-1043", 0.8)

print(a.tracking, a.weight_kg)
print(b.tracking, b.weight_kg)
print(vars(a))

Try it: Try Parcel("QX-1044") with only one argument and read the error: __init__ requires both values.

Parcel("QX-1042", 2.5) creates an empty object, then calls __init__(new_object, "QX-1042", 2.5). Inside, self.tracking = tracking stores the argument as an attribute on that object. vars(a) shows the attributes an object holds as a dictionary — handy for checking your work. The next lesson, __init__ and attributes, looks at self and __init__ in detail.

Objects in collections

Objects are values like any other: you can put them in lists, pass them to functions and return them. A list of Parcel objects replaces a list of dictionaries, and p.weight_kg replaces p["weight_kg"].

Working with a list of objects
class Parcel:
    def __init__(self, tracking, weight_kg):
        self.tracking = tracking
        self.weight_kg = weight_kg

van = [Parcel("QX-1042", 2.5), Parcel("QX-1043", 0.8), Parcel("QX-1044", 12.0)]

total = 0
for parcel in van:
    total += parcel.weight_kg
print("Load:", total, "kg")

heavy = [p.tracking for p in van if p.weight_kg > 2]
print("Heavy:", heavy)

Try it: Add a fourth parcel weighing 7.5 kg and check that the load and the heavy list update.

Predict the output

class Counter:
    def __init__(self, start):
        self.value = start

a = Counter(5)
b = Counter(5)
c = a
a.value = 9
print(a.value, b.value, c.value)
print(a is c, a is b)

Guided practice · ●difficulty 1 of 5 · about 4 min

Define a Parcel class

Define a class Parcel whose __init__ takes tracking and weight_kg and stores them as attributes with the same names.

p = Parcel("QX-2001", 3.2)
p.tracking    # "QX-2001"
p.weight_kg   # 3.2

Every parcel must keep its own values: creating a second parcel must not change the first.

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Challenge · ●●difficulty 2 of 5 · about 8 min

Turn dictionaries into objects

A school exports its gradebook as a list of dictionaries. Convert it to objects.

  1. Define a class Student whose __init__ takes name and score and stores them as attributes name and score.
  2. Write a function to_students(rows) that takes a list of dictionaries with keys "name" and "score" and returns a list of Student objects in the same order.
  3. Write a function top_student(students) that returns the name of the student with the highest score (the first one if several tie), or None for an empty list.
rows = [{"name": "Huda", "score": 81}, {"name": "Omar", "score": 94}]
students = to_students(rows)
students[1].name           # "Omar"
top_student(students)      # "Omar"
top_student([])            # None

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Debugging · ●●difficulty 2 of 5 · about 6 min

Fix the room report

A hotel switched its room records from dictionaries to Room objects. Since then the report function crashes with:

TypeError: 'Room' object is not subscriptable

beds_available(rooms) should return the total number of beds in rooms that are not occupied. Fix the program so it works with Room objects. Don't change how rooms are created.

rooms = [Room(101, 2, False), Room(102, 3, True), Room(103, 1, False)]
beds_available(rooms)   # 3

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Edge cases · ●●difficulty 2 of 5 · about 6 min

Find the lightest parcel

Using this Parcel class (include it in your code):

class Parcel:
    def __init__(self, tracking, weight_kg):
        self.tracking = tracking
        self.weight_kg = weight_kg

write lightest(parcels) that returns the tracking code of the lightest parcel. Handle the awkward cases:

  • an empty list returns None;
  • if several parcels share the lowest weight, return the one that appears first;
  • parcels with a weight of 0 (documents envelopes) count normally — 0 is a real weight.
lightest([Parcel("A1", 2.0), Parcel("A2", 0.5)])   # "A2"
lightest([])                                         # None

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Summary

  • A class defines a new type; objects (instances) are created by calling the class, like Parcel("QX-1", 2.5).
  • Each object has its own attributes, read and set with dot notation: p.weight_kg.
  • __init__ runs automatically when an object is created and stores its starting attributes on self.
  • Objects can be stored in lists, passed to functions and returned like any other value.