Passing Data via the Constructor
Every value a Country needs must arrive through its constructor, deliberately, at creation time โ exactly like React props arrive from a parent. This session makes that discipline explicit.
Data flows in through the constructor, explicitly, every time.
1. Learning Objective
By the end of this session you will be able to:
- Explain why requiring all data through __init__ parameters is a deliberate discipline, not an accident
- Use keyword arguments when constructing an instance for clarity
- Add a simple default value to a constructor parameter
- Explain the risk of a class silently reading global state instead of its own constructor arguments
- Distinguish "construction-time data" from "data computed later by a method"
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 Country(name="Kenya", region="Africa", population=54000000), why write the argument names explicitly instead of just Country("Kenya", "Africa", 54000000)?
Given def __init__(self, name, region, population=0):, what happens with Country("Kenya", "Africa")?
population in the call falls back to 0.Why is it considered bad practice for a class's __init__ to silently read a global variable instead of receiving that value as a parameter?
__init__(self, ...) โ and you cannot easily create a second instance with different data, or test the class in isolation. Passing the value in as a parameter makes the dependency visible and explicit.You want two Country instances that should be genuinely different, e.g. for testing. Which approach is more reliable?
"Construction-time data" (passed into __init__) versus "data computed later" โ which is population if it never changes after creation, vs. a method like was_founded_before(year) that computes True/False on demand?
__init__. A value that depends on an argument supplied at call time (like a comparison year) is a method's job, computed fresh each call rather than stored as an attribute.3. The Concept โ Constructor Arguments as an Explicit Contract
Data flows in one direction: explicit arguments in, an initialized instance out. Nothing is read from anywhere else.
Recap: __init__ already does this
Since Session 13, every Country instance has received its data through __init__ parameters. This session is about treating that as a deliberate rule, not an implementation detail โ and drawing the parallel to how data flows into a React component as props in the original course.
class Country:
def __init__(self, name, region, population):
self.name = name
self.region = region
self.population = population
# All data arrives explicitly, at construction time
kenya = Country(name="Kenya", region="Africa", population=54000000)
Keyword arguments for clarity
As a constructor grows past two or three parameters, positional calls become error-prone โ it is easy to swap two values of the same type by accident. Keyword arguments (Session 09) remove that risk entirely.
# Risky โ easy to accidentally swap two strings
peru = Country("Peru", "Americas", 33000000)
# Safer โ self-documenting, order-independent
peru = Country(name="Peru", region="Americas", population=33000000)
A default value on a constructor parameter
Just like any function parameter, a constructor parameter can default to a value when the caller doesn't know it yet.
class Country:
def __init__(self, name, region, population=0):
self.name = name
self.region = region
self.population = population
unknown = Country(name="New Territory", region="Unclaimed")
print(unknown.population) # 0 โ default used
Why NOT to read global state inside __init__
A tempting shortcut is to have a class silently pull data from a global variable instead of a parameter. This hides the class's real dependencies and makes every instance implicitly coupled to shared state โ exactly the kind of bug Session 05 warned about with shared references.
# Risky pattern โ DO NOT do this
DEFAULT_REGION = "Africa"
class RiskyCountry:
def __init__(self, name):
self.name = name
self.region = DEFAULT_REGION # hidden dependency, not visible in the signature!
# Safer โ the dependency is visible right in the constructor call
class Country:
def __init__(self, name, region):
self.name = name
self.region = region
safe = Country(name="Kenya", region="Africa") # nothing hidden
4. Lab
What you will build
A file called construction_lab.py.
Step-by-step instructions
Create the file with a Country class using a default population
# construction_lab.py
class Country:
def __init__(self, name, region, population=0):
self.name = name
self.region = region
self.population = population
Construct three instances using keyword arguments only
kenya = Country(name="Kenya", region="Africa", population=54000000)
peru = Country(name="Peru", region="Americas", population=33000000)
unknown = Country(name="New Territory", region="Unclaimed")
print(kenya.population, peru.population, unknown.population)
Write the risky global-state version and the safe version side by side
Do not delete either โ keep both in the file with comments explaining the difference.
DEFAULT_REGION = "Africa"
class RiskyCountry:
def __init__(self, name):
self.name = name
self.region = DEFAULT_REGION # hidden dependency
class SafeCountry:
def __init__(self, name, region):
self.name = name
self.region = region
risky = RiskyCountry("Somewhere")
safe = SafeCountry(name="Somewhere", region="Africa")
Prove the risky version breaks when the global changes
Change DEFAULT_REGION after construction and observe what happens to a NEW risky instance versus the existing one.
DEFAULT_REGION = "Europe"
another_risky = RiskyCountry("Somewhere Else")
print(risky.region) # "Africa" โ set before the change
print(another_risky.region) # "Europe" โ silently different, no argument changed!
Write a one-sentence comment explaining why SafeCountry does not have this problem
5. Expected Files Changed
| File | Action | Why |
|---|---|---|
construction_lab.py |
Created | Demonstrates keyword construction, defaults, and the risk of hidden global dependencies. |
docs/sessions/session-15/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 construction_lab.py docs/sessions/session-15/index.html
git commit -m "session-15: require explicit constructor arguments instead of hidden global state"
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 Country(name="Kenya", region="Africa", population=54000000), why write the argument names explicitly instead of just Country("Kenya", "Africa", 54000000)?
Given def __init__(self, name, region, population=0):, what happens with Country("Kenya", "Africa")?
population in the call falls back to 0.Why is it considered bad practice for a class's __init__ to silently read a global variable instead of receiving that value as a parameter?
__init__(self, ...) โ and you cannot easily create a second instance with different data, or test the class in isolation. Passing the value in as a parameter makes the dependency visible and explicit.You want two Country instances that should be genuinely different, e.g. for testing. Which approach is more reliable?
"Construction-time data" (passed into __init__) versus "data computed later" โ which is population if it never changes after creation, vs. a method like was_founded_before(year) that computes True/False on demand?
__init__. A value that depends on an argument supplied at call time (like a comparison year) is a method's job, computed fresh each call rather than stored as an attribute.9. Reflection Questions
Think through these after the post-quiz. No right answer โ they are for discussion.
- Where else in the labs so far might you have accidentally relied on a value "just being there" instead of passing it explicitly?
- What is the cost of always using keyword arguments (more typing) versus the benefit (clarity, safety)? Where is that tradeoff worth it and where is it not?
- How does this session's "explicit data in, explicit instance out" pattern connect back to the reference/mutation lessons from Session 05?
- Can you think of a legitimate use for a class reading module-level state, where it would NOT be a hidden dependency problem?
10. What Breaks If This Knowledge Is Missing?
- Hard-to-test classes: A class that reads global or module-level state instead of constructor arguments cannot be tested in isolation โ every test now depends on setting up (and tearing down) that global correctly, which is exactly the kind of fragile test suite Layer 5 will teach you to avoid.
- Composition (Session 16): The next session builds one class out of several others. If each class does not have a clean, explicit set of constructor dependencies, composing them together becomes guesswork instead of straightforward assembly.
- The data layer (Layer 4): The CountryRepository we build in Session 28 depends on this discipline directly โ it is constructed with its data source as an explicit argument, which is exactly what makes it possible to swap in fake data for tests later.
11. What We Learned
Python concept mastered: Constructor arguments as an explicit, self-documenting contract โ keyword arguments, defaults, and the risk of hidden global dependencies.
Unlocks: Every class we build from here forward will receive its dependencies explicitly, making it possible to compose and test them independently.
Next session: Session 16 โ Composition โ Objects Containing Objects. We build a class whose attribute is itself another object โ composing several small classes into a coherent whole.