Structuring Your Data

Defining Your Own Types: Classes


Learning Objectives

  • You can explain the limitations of using a dictionary as a record.
  • You can define a class with an __init__ method that sets up its attributes.
  • You can create instances of a class and read their attributes with dot notation.
  • You can distinguish attribute access (session.minutes) from key access (session["minutes"]).

A dictionary fixed the main problem with list-of-rows: the fields have names now. But a dictionary is still a loose collection of keys; a typo in a key is not an error, and there is no guarantee that every session has the same keys. The behaviour that operates on sessions is still separate from the data, and there is no way to say “this is what a session is” in one place.

This chapter introduces classes, which let you define your own type — a Session that is always shaped like a session. This chapter is the third and last step of the part’s arc from row[1] to session.minutes.

A dictionary is the right tool when the keys genuinely vary, or when you are assembling data whose shape you do not control. But a session has a fixed shape — every session has exactly a subject, a number of minutes, and a date — and for fixed-shape records, classes are a better fit than dictionaries.

Defining a Class

A class defines a new type. Writing a class is writing down, in one place, what every value of that type looks like. Here is a Session:

class Session:
def __init__(self, subject, minutes, date):
self.subject = subject
self.minutes = minutes
self.date = date

There is a fair amount of new syntax here, so take a moment to read it through.

class Session: begins the definition of a new type called Session. By convention, class names start with a capital letter, which is how you tell Session (a type) from session (a value of that type).

Inside, def __init__(self, subject, minutes, date): defines a special method — in some languages called a constructor — that runs automatically whenever you create a new Session. Its job is to set up the new object’s data. The name __init__ — short for “initialise”, with two underscores on each side — is special: Python looks for exactly that name and calls it for you. You do not call it directly.

The first parameter, self, is the object being created. Every method’s first parameter is self, and it refers to the particular object the method is working on. When __init__ runs, self is the new, blank Session, and the three assignments fill it in:

self.subject = subject
self.minutes = minutes
self.date = date

Each line takes one of the values passed in and stores it on the object under a name. self.subject = subject means “store the subject argument on this object, as its subject attribute.” The name on the left (after self.) is the attribute; the name on the right is just the parameter. They are spelled the same here by convention, but they are different things — one is a slot on the object, the other is an argument that arrived.

The role of self

0 / 5 points

Inside a constructor such as def __init__(self, title, priority):, what does self refer to?

Creating an Instance

With the class defined, you create a Session by calling the class name like a function, passing the values that __init__ expects — but not self, which Python supplies automatically:

session = Session("math", 45, "2024-03-10")

This creates a new Session object — an instance of the class — and runs __init__ with self bound to the new object and the other three parameters set to "math", 45, and "2024-03-10". When __init__ finishes, session is a fully set-up Session.

You read its data with dot notation:

print(session.subject) # math
print(session.minutes) # 45
print(session.date) # 2024-03-10

session.minutes means “the minutes attribute of session.”

Attribute access

0 / 5 points

A Ticket object stores its priority as an attribute named priority.

Which expression reads that value?

Now, you know the three ways of accessing data, each from a different kind of structure:

row[1] # positional from lists
session["minutes"] # key access from dictionaries
session.minutes # attribute access from classes

Nested Structures

A class can also hold more complex data. For example, a StudyLog class could hold a list of Session objects as its sessions attribute.

class StudyLog:
def __init__(self, sessions):
self.sessions = sessions

The pattern for accessing the data is the same: you read the sessions attribute of a StudyLog instance, and then you can read the attributes of each Session in that list:

log = StudyLog([Session("math", 45, "2024-03-10")])
print(log.sessions[0].subject) # math

Benefits from a Class

A class makes the expected shape explicit and checks construction.

The class definition says, in one place, that a Session has a subject, minutes, and a date. Anyone reading the code can look at __init__ and know exactly what a session is. With a dictionary, the shape was implied by usage scattered across the program; now it is stated.

This means that mistakes surface sooner. A misspelled attribute is an error at the moment you use it, not a silent default much later:

print(session.mintues)
# AttributeError: 'Session' object has no attribute 'mintues'

Compare this to session.get("mintues", 0) on a dictionary, which would have quietly returned 0 and let the bug travel. The class catches the typo loudly, near where it was made.

Similarly, construction of an instance is checked. Creating a Session requires exactly the arguments __init__ declares. Leave one out and Python complains immediately:

Session("math", 45)
# TypeError: __init__() missing 1 required positional argument: 'date'

You cannot accidentally build a session that is missing its date, the way you could accidentally build a dictionary without a "date" key. The type enforces its own shape.

Classes can also have methods, which are functions that run on an instance and can access its attributes. With a class, you can say “this is what a session is, and this is what you can do with a session” in one place. Methods are the subject of the next chapter.

Check Your Understanding

  1. Name two things about a record’s shape that a dictionary does not guarantee but a class does.
  2. What is the role of self in the __init__ method?
  3. When you write Session("math", 45, "2024-03-10"), why do you not pass a value for self?
  4. What happens if you read session.mintues (misspelled) on a Session object? How does that differ from session.get("mintues", 0) on a dictionary?
  5. A class is a good fit for a session but not necessarily for data whose set of keys varies from record to record. Why?

AI Study Aid

Create a chapter diagram

We're looking into to what extent AI-generated diagrams could help with studying.

Use this study aid to generate an AI-generated visual summary of the material you just studied. Each diagram style emphasizes the content in a different way, so you can choose the focus that feels most useful.

Using the diagram generator is voluntary and does not affect your course progress. We encourage you to try it out and see if it helps, but it's totally up to you! Your answers help us understand how to make better study aids in the future.

Diagrams tried: 0