Class Anatomy โ __init__ and Attributes
An empty class is not useful. This session gives every Country instance its own data, using the constructor method __init__ and instance attributes.
Every instance gets its own independent slots of data.
1. Learning Objective
By the end of this session you will be able to:
- Define __init__ with parameters and assign them to self attributes
- Explain what self refers to and why every method needs it as the first parameter
- Create multiple instances with different attribute values and confirm they do not interfere with each other
- Read and update an instance attribute from outside the class
- Compare attribute access (dot notation) to the dictionary bracket access from Session 05
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):\n self.name = name, what does self.name = name do?
self refers to the specific instance currently being built. Assigning to self.name stores the value ON that instance, so it persists and is readable as instance.name after construction finishes.Why does every method inside a class, including __init__, take self as its first parameter?
c.some_method(), Python automatically passes c itself as the first argument. By convention we name that parameter self, and it is how the method accesses that specific instance's data.Given k = Country("Kenya") and g = Country("Ghana"), what is k.name?
Country(...) runs __init__ fresh, with its own self. k and g are separate instances, each with their own independent name attribute.How do you read an instance attribute from outside the class, e.g. on a variable k?
Can you change an instance attribute after construction, e.g. k.name = "Kenya (updated)"?
3. The Concept โ __init__ and Instance Attributes
Country("Kenya") silently becomes __init__(new_blank_instance, "Kenya") โ self IS that new instance.
The constructor: __init__
A class gains real data through a special method called __init__, which Python calls automatically every time you construct a new instance. Its job is to set up that instance's starting attributes.
class Country:
def __init__(self, name, region, population):
self.name = name
self.region = region
self.population = population
k = Country("Kenya", "Africa", 54000000)
print(k.name) # "Kenya"
print(k.region) # "Africa"
print(k.population) # 54000000
self โ a reference to the specific instance
When Python runs Country("Kenya", "Africa", 54000000), it first creates a blank instance, then calls __init__(that_instance, "Kenya", "Africa", 54000000) โ the instance itself is silently passed as the first argument. We name that parameter self by convention, and use it to attach data to that specific instance.
class Country:
def __init__(self, name):
print("self is:", self) # the instance being built
self.name = name # attach data to THIS instance
k = Country("Kenya")
# self is: <__main__.Country object at 0x...>
Every instance holds its own independent data
Because __init__ runs fresh for each instance, with a different self each time, instances never share attribute values by default โ exactly like two separate dictionaries never share keys.
k = Country("Kenya")
g = Country("Ghana")
print(k.name) # "Kenya"
print(g.name) # "Ghana" โ completely independent of k.name
Dot notation vs Session 05's bracket notation
Recall reading a dictionary value: country["name"]. An instance attribute is read the same conceptual way, but with dot notation instead of brackets, and no risk of a KeyError โ accessing an attribute that truly doesn't exist raises AttributeError instead, which we'll handle the same way we handled KeyError in Session 11.
country_dict = {"name": "Kenya"}
print(country_dict["name"]) # dict โ bracket access
k = Country("Kenya", "Africa", 54000000)
print(k.name) # instance โ dot access
# Attributes are mutable, just like dict values
k.name = "Kenya (updated)"
print(k.name)
Country now genuinely holds data of its own. Next session we give it behavior โ methods that operate on that data without needing it passed in as an argument.
4. Lab
What you will build
A file called country_init.py.
Step-by-step instructions
Create the file and define __init__
# country_init.py
class Country:
def __init__(self, name, region, population):
self.name = name
self.region = region
self.population = population
Create two instances with different data
kenya = Country("Kenya", "Africa", 54000000)
peru = Country("Peru", "Americas", 33000000)
print(kenya.name, kenya.region, kenya.population)
print(peru.name, peru.region, peru.population)
Prove the instances are independent
Change one instance's attribute and confirm the other is untouched.
kenya.population = 55000000
print("kenya.population:", kenya.population) # 55000000
print("peru.population:", peru.population) # still 33000000 โ unaffected
Print self inside __init__ for both instances
Add a temporary print(self) line inside __init__ to see the two different instance addresses.
class CountryDebug:
def __init__(self, name):
print("Building instance:", self, "with name", name)
self.name = name
CountryDebug("Kenya")
CountryDebug("Peru")
Read one attribute with dot notation and compare it to Session 05's dict syntax in a comment
print(kenya.name)
# Compare to Session 05: country_dict["name"] โ same idea, different syntax
5. Expected Files Changed
| File | Action | Why |
|---|---|---|
country_init.py |
Created | A real Country class with __init__ and instance attributes. |
docs/sessions/session-13/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_init.py docs/sessions/session-13/index.html
git commit -m "session-13: give Country real attributes via __init__"
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):\n self.name = name, what does self.name = name do?
self refers to the specific instance currently being built. Assigning to self.name stores the value ON that instance, so it persists and is readable as instance.name after construction finishes.Why does every method inside a class, including __init__, take self as its first parameter?
c.some_method(), Python automatically passes c itself as the first argument. By convention we name that parameter self, and it is how the method accesses that specific instance's data.Given k = Country("Kenya") and g = Country("Ghana"), what is k.name?
Country(...) runs __init__ fresh, with its own self. k and g are separate instances, each with their own independent name attribute.How do you read an instance attribute from outside the class, e.g. on a variable k?
Can you change an instance attribute after construction, e.g. k.name = "Kenya (updated)"?
9. Reflection Questions
Think through these after the post-quiz. No right answer โ they are for discussion.
- If you forgot to write
self.name = nameand just wrotename = nameinside __init__, what do you predict would happen when you triedkenya.nameafterward? Try it. - Why does Python require self to be listed explicitly as the first parameter instead of making it implicit like some other languages do?
- What is the practical difference between a dictionary's keys and a class instance's attributes, now that you've used both?
- Can you think of a rule (like Session 11's population validation) that __init__ should probably enforce, that it does not yet?
10. What Breaks If This Knowledge Is Missing?
- The self.x = x omission bug: Forgetting
self.in an assignment inside __init__ is an extremely common beginner mistake โ the value is assigned to a local variable that vanishes when the method returns, and the instance ends up with no such attribute at all, causing an AttributeError later when you try to read it. - Methods (Session 14): Every method you write from the next session forward relies on the exact same self mechanism you just learned. If self is not solid now, method definitions will look like unexplained boilerplate.
- Constructor validation (Layer 4 data contracts): In Session 30 we will formalize what a "valid" Country looks like using type hints and dataclasses โ that entire session assumes you deeply understand what __init__ is actually doing today.
11. What We Learned
Python concept mastered: __init__ as the constructor, self as the reference to the instance being built, and instance attributes as independent per-instance data.
Unlocks: Country instances can now hold real, independent data โ the foundation every remaining session in the project builds on.
Next session: Session 14 โ Instance Methods. We give Country behavior โ methods that operate on its own data without needing that data passed in as an argument every time.