Structuring Your Data

Adding Behavior: Methods


Learning Objectives

  • You can define a method on a class and call it on an instance.
  • You can explain the role of self in a method.
  • You can write a __str__ method to give an object a readable display.
  • You can recognise when behaviour belongs on the class rather than in a free-standing function.

In the previous chapter, we created classes that hold data with named fields. But the classes cannot yet do anything — asking whether a session is long, or formatting one for display, would still be a separate function sitting apart from the data. This chapter adds methods: functions that live on the class and operate on an instance.

A Method Is a Function on the Class

Recall the Session from the previous chapter:

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

__init__ is already a method — it is a function defined inside the class. A method you write yourself looks the same. Suppose you often want to know whether a session counts as “long” (sixty minutes or more). As a free-standing function that would be:

def is_long(session):
return session.minutes >= 60

As a method, it moves inside the class and takes self as its first parameter:

class Session:
def __init__(self, subject, minutes, date):
self.subject = subject
self.minutes = minutes
self.date = date
def is_long(self):
return self.minutes >= 60

self is the same self as in __init__: it is the particular session the method is working on. Inside is_long, self.minutes is that session’s minutes. The method asks whether they reach sixty and returns the answer.

You call a method with dot notation, the same way you read an attribute, but with parentheses because it is a call:

session = Session("math", 90, "2024-03-10")
print(session.is_long()) # True

Notice you do not pass self. When you write session.is_long(), Python automatically supplies session as self. That is the whole trick of method calls: session.is_long() is really “call is_long with self set to session.” The object before the dot becomes self inside the method.

Right Instance with self

The point of self is that the same method works on any instance, and each call operates on the right one:

short = Session("history", 30, "2024-03-11")
long = Session("physics", 90, "2024-03-10")
print(short.is_long()) # False -- self is `short`, self.minutes is 30
print(long.is_long()) # True -- self is `long`, self.minutes is 90

One method definition, two objects, two answers. self is how the method knows which session it was asked about. This is the difference between a method and a free-standing function: the method is attached to the data, and self is its handle on the specific instance it was called on.

The instance before the dot

0 / 5 points

Two sessions use the same is_long method:

short = Session("history", 30, "2026-04-01")
long = Session("physics", 90, "2026-04-02")

When Python evaluates long.is_long(), what does self refer to inside the method?

A Display Method: __str__

By default, printing an object is unhelpful:

session = Session("math", 45, "2024-03-10")
print(session)
# <__main__.Session object at 0x7f3a2c1d4e50>

That tells you the type and a memory address, neither of which you care about. Python lets you fix this with another specially named method, __str__, which returns a string meant for people to read. Like __init__, the double-underscore name is one Python looks for automatically — you define it, and Python calls it when the object is converted to a string:

class Session:
def __init__(self, subject, minutes, date):
self.subject = subject
self.minutes = minutes
self.date = date
def is_long(self):
return self.minutes >= 60
def __str__(self):
return f"{self.subject.title()}: {self.minutes} minutes on {self.date}"

Now the object shows itself usefully:

session = Session("math", 45, "2024-03-10")
print(session)
# Math: 45 minutes on 2024-03-10

This method is still just a method: it takes self, reads the object’s attributes, and returns a string. What makes it special is the name. When you write str(session) or print(session), Python looks for __str__ and uses the string it returns. A good __str__ pays for itself the first time you print an object and see useful information instead of a memory address.

Add a __str__ method

0 / 5 points

The starter file contains a Plant class with name, height_cm, and location.

Add a method named __str__(self).

The method must return a string in this exact format:

NAME in LOCATION (HEIGHT cm)

The plant name and location must be title-cased — use .title(). For example:

str(Plant("basil", 18, "kitchen"))

returns:

Basil in Kitchen (18 cm)

When print(plant) is used, Python should show the same text.

Requirements:

  • keep the constructor working,
  • read the object’s attributes with self,
  • do not print anything,
  • and do not use input(...).

When Behaviour Belongs on the Class

Not every function that touches a session should become a method. The useful test is whether the behaviour is fundamentally about one session, using mostly that session’s own data.

The methods is_long and __str__ pass the test: each concerns a single session and reads only that session’s fields. They belong on Session.

A function like totals_by_subject, on the other hand, is about a collection of sessions, not any single one. It does not belong on Session — there is no one session it is “about”. That sort of a function should operate on a collection class.

Method or helper

0 / 5 points

Which behavior most clearly belongs as a method on one Ticket object?

Methods and Collection Classes

Let’s revisit the StudyLog class from the previous chapter, which holds a list of sessions:

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

This is a class that holds a collection of records, and it is the natural home for methods that operate on the whole collection. For example, totals_by_subject belongs on StudyLog — it is about the whole log, not any one session — and it would be rewritten as:

class StudyLog:
def __init__(self, sessions):
self.sessions = sessions
def totals_by_subject(self):
totals = {}
for session in self.sessions:
subject = session.subject
minutes = session.minutes
totals[subject] = totals.get(subject, 0) + minutes
return totals

Now, a programmer using the StudyLog and Session classes can create a log, add sessions to it, and ask the log to summarise itself — all with methods that live on the relevant classes and operate on their data. The behaviour is organised around the data it operates on, and the programmer does not have to know about separate functions or how they work.

log = StudyLog([
Session("math", 45, "2024-03-10"),
Session("physics", 90, "2024-03-10"),
Session("history", 30, "2024-03-11"),
Session("math", 60, "2024-03-11"),
Session("math", 25, "2024-03-12"),
])
print(log.totals_by_subject())
# {'math': 130, 'physics': 90, 'history': 30}

Instances as Function Arguments

The previous examples have focused on methods, but you can also pass instances to free-standing functions. For example, the format_session function from the previous chapter would be rewritten as:

def format_session(session):
return f"{session.subject}: {session.minutes} min on {session.date}"

Similarly, a function that would check whether a session is “long” would be:

def is_long(session):
return session.minutes >= 60

And a function that would be given a list of sessions and return the total minutes for a subject would be:

def total_minutes_for_subject(sessions, subject):
total = 0
for session in sessions:
if session.subject == subject:
total += session.minutes
return total

The decision on whether something should be a method that lives on the class or a free-standing function that takes instances as arguments is a design choice. The test is whether the behaviour is fundamentally about one instance, using mostly that instance’s own data. If so, it belongs on the class as a method; if not, it belongs on a collection class or as a free-standing function.

Check Your Understanding

  1. What does the first parameter of a method, self, refer to?
  2. When you call session.is_long(), where does the self inside is_long come from?
  3. What is __str__ for, and when does Python call it?
  4. The chapter gives a test for whether a piece of behaviour belongs on a class. What is that test?
  5. Why does a function like totals_by_subject not belong on the Session class?

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