Instance Methods
A class with only attributes is still just a fancier dictionary. Methods are functions that live on the class and operate on an instance's own data through self.
Behavior that lives with the data it operates on.
1. Learning Objective
By the end of this session you will be able to:
- Define an instance method that reads self attributes
- Call a method on an instance using dot notation
- Write a method that both reads and updates self attributes
- Explain why a method does not need its data passed in as an argument, unlike the standalone functions from Layer 1
- Compare a method call to the equivalent standalone-function call from Session 08
2. Pre-Coding Quiz
Answer these before reading the concept explanation or writing any code. It is fine to get these wrong โ that is the point. You need 4/5 to proceed to the lab.
Given class Country:\n def __init__(self, name, population):\n self.name = name\n self.population = population\n def summary(self):\n return f"{self.name}: {self.population}", what does k.summary() return for k = Country("Kenya", 54000000)?
k.summary() runs the method with self bound to k. Inside, self.name is "Kenya" and self.population is 54000000, producing "Kenya: 54000000".Why does calling k.summary() not require you to pass k as an argument yourself, e.g. summary(k)?
k.summary() is exactly the mechanism from Session 13 in reverse: Python automatically supplies k as the first argument (self) to summary. You only supply the remaining arguments explicitly.Given a method def grow_population(self, amount):\n self.population += amount, what does k.grow_population(1000000) do?
self.population inside a method changes the actual instance k โ the same instance you called the method on, in place. Nothing needs to be reassigned.Compare method calls to the Layer 1 approach: In Session 08 we wrote get_population(country_dict). What is the equivalent as a method?
If Country defines a method called region_label(self), can you call it as Country.region_label(k) instead of k.region_label()?
k.region_label() and Country.region_label(k) do exactly the same thing โ the first form is simply what everyone writes in practice.3. The Concept โ Instance Methods
The dot-call is shorthand: k.summary() and Country.summary(k) are the same call.
A method is a function defined inside a class
Like __init__, every method takes self as its first parameter, giving it access to that instance's own attributes โ no need to pass the data in as an argument, because the method already lives on the object that owns it.
class Country:
def __init__(self, name, region, population):
self.name = name
self.region = region
self.population = population
def summary(self):
return f"{self.name} ({self.region}): pop. {self.population:,}"
k = Country("Kenya", "Africa", 54000000)
print(k.summary()) # "Kenya (Africa): pop. 54,000,000"
What the dot-call actually does
When you write k.summary(), Python translates this into Country.summary(k) behind the scenes โ k is automatically supplied as self. This is the exact same mechanism from Session 13's __init__, just used for a different method.
print(k.summary()) # normal way
print(Country.summary(k)) # exactly equivalent โ proves it
A method that mutates the instance
Methods can update self's attributes just like the standalone functions from Layer 1 updated dict keys โ except now the mutation logic lives with the data it protects.
class Country:
def __init__(self, name, population):
self.name = name
self.population = population
def grow_population(self, amount):
if amount < 0:
raise ValueError("amount must be non-negative")
self.population += amount
k = Country("Kenya", 54000000)
k.grow_population(1000000)
print(k.population) # 55000000 โ mutated in place, no reassignment needed
Standalone function (Layer 1) vs method (Layer 2) โ side by side
This is the entire conceptual leap of Layer 2: the data moved inside the object, so the function moved with it.
# Layer 1 style โ Session 08
def get_population(country_dict):
return country_dict["population"]
print(get_population({"population": 54000000}))
# Layer 2 style โ this session
class Country:
def __init__(self, population):
self.population = population
def get_population(self):
return self.population
k = Country(54000000)
print(k.get_population()) # no argument needed โ self already has it
4. Lab
What you will build
A file called country_methods.py.
Step-by-step instructions
Create the file with __init__ and a summary method
# country_methods.py
class Country:
def __init__(self, name, region, population):
self.name = name
self.region = region
self.population = population
def summary(self):
return f"{self.name} ({self.region}): pop. {self.population:,}"
Create an instance and call summary()
kenya = Country("Kenya", "Africa", 54000000)
print(kenya.summary())
Prove k.summary() and Country.summary(k) are the same call
print(kenya.summary() == Country.summary(kenya)) # True
Add a mutating grow_population method with validation
Reuse the ValueError pattern from Session 11.
class Country:
def __init__(self, name, region, population):
self.name = name
self.region = region
self.population = population
def summary(self):
return f"{self.name} ({self.region}): pop. {self.population:,}"
def grow_population(self, amount):
if amount < 0:
raise ValueError(f"amount must be non-negative, got {amount}")
self.population += amount
kenya = Country("Kenya", "Africa", 54000000)
kenya.grow_population(1000000)
print(kenya.summary())
Call grow_population with a negative amount inside try/except
Reuse the try/except pattern from Session 11 โ confirm the exception is raised and caught.
try:
kenya.grow_population(-500)
except ValueError as e:
print("Rejected:", e)
5. Expected Files Changed
| File | Action | Why |
|---|---|---|
country_methods.py |
Created | Adds summary() and grow_population() instance methods to Country. |
docs/sessions/session-14/index.html |
Created | This session document. |
6. Commit Checkpoint
Once the lab is complete and you can explain every line, make this exact commit:
git add country_methods.py docs/sessions/session-14/index.html
git commit -m "session-14: add summary and grow_population instance methods"
7. Code Review Checklist
Go through your code line by line and check each item:
8. Post-Coding Quiz
Same 5 questions. Take it again now that you have written and run the code. You need 4/5 to mark this session complete.
Given class Country:\n def __init__(self, name, population):\n self.name = name\n self.population = population\n def summary(self):\n return f"{self.name}: {self.population}", what does k.summary() return for k = Country("Kenya", 54000000)?
k.summary() runs the method with self bound to k. Inside, self.name is "Kenya" and self.population is 54000000, producing "Kenya: 54000000".Why does calling k.summary() not require you to pass k as an argument yourself, e.g. summary(k)?
k.summary() is exactly the mechanism from Session 13 in reverse: Python automatically supplies k as the first argument (self) to summary. You only supply the remaining arguments explicitly.Given a method def grow_population(self, amount):\n self.population += amount, what does k.grow_population(1000000) do?
self.population inside a method changes the actual instance k โ the same instance you called the method on, in place. Nothing needs to be reassigned.Compare method calls to the Layer 1 approach: In Session 08 we wrote get_population(country_dict). What is the equivalent as a method?
If Country defines a method called region_label(self), can you call it as Country.region_label(k) instead of k.region_label()?
k.region_label() and Country.region_label(k) do exactly the same thing โ the first form is simply what everyone writes in practice.9. Reflection Questions
Think through these after the post-quiz. No right answer โ they are for discussion.
- Rewrite grow_population as a Layer-1-style standalone function taking a dict. How many more characters does the caller have to type versus the method version?
- Why does putting validation logic (the ValueError check) inside the method make it harder to accidentally bypass than a separate validation function would?
- What would happen if you defined grow_population without self as the first parameter? Try it and read the error message carefully.
- Can every standalone function from Layer 1 become a method? Can you think of one that could not?
10. What Breaks If This Knowledge Is Missing?
- Missing self bugs: Forgetting
selfas a method's first parameter produces a confusing TypeError about argument counts when the method is called normally โ a very common early mistake that this session should immunize you against. - Passing data (Session 15): Right now every instance is built by passing all fields directly to __init__. In the next session, we formalize this as "props" flowing into an object at construction time, drawing an explicit line back to the original React course's prop pattern.
- Testing methods (Layer 5): Every unit test you write from Session 33 onward calls methods on instances exactly the way this lab does. If method calls are not second nature, reading test code will be much harder.
11. What We Learned
Python concept mastered: Instance methods โ self-bound functions that read and mutate an instance's own data without needing it passed in explicitly.
Unlocks: Country is now a genuine object: data plus the behavior that belongs with it. This is the last piece needed before we start composing objects together.
Next session: Session 15 โ Passing Data via the Constructor. We formalize how data flows into an object at construction time, and start passing whole objects (not just primitive values) into other objects.