Layer 2 Session 17 Object-Oriented Basics

Conditional Logic in Methods

Real data has gaps. A method needs to behave sensibly when the collection is empty, a value is missing, or a search finds nothing at all.

? True False

A method that behaves differently depending on what it finds.

Estimated time: 35โ€“40 minutes  ยท  Pre-quiz โ†’ Concept โ†’ Lab โ†’ Commit โ†’ Post-quiz

1. Learning Objective

By the end of this session you will be able to:

  • Write a method that branches based on the state of the instance
  • Use a ternary (conditional) expression for a short either/or value
  • Handle an empty list gracefully without a crash or a misleading result
  • Use the "truthy" nature of empty lists/strings in an if condition
  • Explain the difference between "no result found" and "an actual error"

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.

Question 1 of 5

Given def total_population(self):\n return sum(c.population for c in self.countries), what does this return if self.countries is an empty list?

sum() of an empty sequence returns 0 by definition โ€” no special handling is needed here. This is worth confirming explicitly rather than assuming.
Question 2 of 5

What does if self.countries: check, given that self.countries is a list?

In Python, empty collections (list, dict, string) are falsy, and non-empty ones are truthy. if self.countries: is idiomatic for "does this list have anything in it?" โ€” equivalent to but more idiomatic than checking length explicitly.
Question 3 of 5

Given label = "Africa" if country.region == "Africa" else "Other", what kind of expression is this?

Python's ternary form is value_if_true if condition else value_if_false. It is an expression (it produces a value) rather than a statement, useful for short either/or assignments.
Question 4 of 5

A method find_by_region(region) returns [] when no country matches. Is returning an empty list the same kind of situation as raising an exception?

A search finding zero matches is a completely normal, expected outcome โ€” not an error. Reserve exceptions (Session 11) for truly invalid states, like a negative population. Conflating "not found" with "broken" makes calling code harder to write correctly.
Question 5 of 5

Given a method that must show a message when there are no countries, which is more idiomatic Python?

if not self.countries: relies on the truthy/falsy behaviour from this session and is the idiomatic Python style. The other two work but are unnecessarily verbose for the same check.

3. The Concept โ€” Conditional Logic in Methods

[]falsy[X]truthy

Empty collections are falsy; anything with at least one item is truthy.

Branching based on instance state

A method can look at self's current attributes and behave differently depending on what it finds โ€” exactly like the plain if-statements from earlier sessions, just now reading self instead of a passed-in argument.

class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries

    def status_message(self):
        if not self.countries:
            return "No countries loaded yet."
        return f"{len(self.countries)} countries loaded."

empty_explorer = CountryExplorer(countries=[])
print(empty_explorer.status_message())  # "No countries loaded yet."

Truthy and falsy collections

Python treats an empty list, empty string, empty dict, 0, and None as falsy in a boolean context. A non-empty version of any of them is truthy. This lets you write if not self.countries: instead of the more verbose if len(self.countries) == 0:.

countries = []
print(bool(countries))  # False โ€” empty list is falsy

countries = [1]
print(bool(countries))  # True โ€” non-empty list is truthy

# Idiomatic style
if not countries:
    print("empty")
else:
    print("has items")

The ternary expression for short either/or values

When a value simply needs to be one of two things based on a condition, a ternary expression is more compact than a full if/else block, and it produces a value directly rather than a side effect.

def region_or_unknown(country):
    return country.region if country.region else "Unknown"

# equivalent longhand
def region_or_unknown_longhand(country):
    if country.region:
        return country.region
    else:
        return "Unknown"

"Not found" is not the same as "broken"

Recall Session 11: exceptions are for genuinely invalid states. A search that legitimately finds nothing should return an empty, valid result โ€” not raise an exception. Confusing the two forces every caller to wrap normal searches in try/except unnecessarily.

class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries

    def find_by_region(self, region):
        # Zero matches is a NORMAL outcome โ€” return an empty list, don't raise
        return [c for c in self.countries if c.region == region]

explorer = CountryExplorer(countries=[])
result = explorer.find_by_region("Antarctica")
print(result)  # [] โ€” a valid, empty answer, no crash, no exception

4. Lab

Lab objective: Add conditional logic to CountryExplorer that handles an empty collection gracefully, using truthy checks and a ternary expression.

What you will build

A file called conditional_lab.py, building on Session 16's CountryExplorer.

Step-by-step instructions

1

Create the file with Country and CountryExplorer

# conditional_lab.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:,}"


class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries
2

Add a status_message() method using not self.countries

class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries

    def status_message(self):
        if not self.countries:
            return "No countries loaded yet."
        return f"{len(self.countries)} countries loaded."

empty = CountryExplorer(countries=[])
loaded = CountryExplorer(countries=[Country(name="Kenya", region="Africa", population=54000000)])
print(empty.status_message())
print(loaded.status_message())
3

Add find_by_region() and confirm zero matches returns [] without a crash

class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries

    def status_message(self):
        if not self.countries:
            return "No countries loaded yet."
        return f"{len(self.countries)} countries loaded."

    def find_by_region(self, region):
        return [c for c in self.countries if c.region == region]

explorer = CountryExplorer(countries=[Country(name="Kenya", region="Africa", population=54000000)])
result = explorer.find_by_region("Antarctica")
print(result)          # []
print(type(result))    # <class 'list'> โ€” never None, never an exception
4

Add a ternary-based method for a country's display capital

Give Country an optional capital, defaulting to None, and add a method that returns it or "Unknown".

class Country:
    def __init__(self, name, region, population, capital=None):
        self.name = name
        self.region = region
        self.population = population
        self.capital = capital

    def display_capital(self):
        return self.capital if self.capital else "Unknown"

k = Country(name="Kenya", region="Africa", population=54000000, capital="Nairobi")
u = Country(name="Unclaimed", region="Antarctica", population=0)
print(k.display_capital())  # "Nairobi"
print(u.display_capital())  # "Unknown"
5

Confirm status_message on an explorer with one country added after construction

explorer = CountryExplorer(countries=[])
print(explorer.status_message())  # "No countries loaded yet."
explorer.countries.append(k)
print(explorer.status_message())  # "1 countries loaded." โ€” note the grammar, discuss in reflection

5. Expected Files Changed

FileActionWhy
conditional_lab.py Created Adds conditional branching, truthy checks, and a ternary display method.
docs/sessions/session-17/index.html Created This session document.
If you find yourself editing any other file, stop. This session touches exactly 2 files.

6. Commit Checkpoint

Once the lab is complete and you can explain every line, make this exact commit:

git add conditional_lab.py docs/sessions/session-17/index.html
git commit -m "session-17: handle empty collections and missing values with conditional logic"
Do not commit until you can answer out loud: "Why does find_by_region return an empty list instead of raising an exception when nothing matches?"

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.

Question 1 of 5

Given def total_population(self):\n return sum(c.population for c in self.countries), what does this return if self.countries is an empty list?

sum() of an empty sequence returns 0 by definition โ€” no special handling is needed here. This is worth confirming explicitly rather than assuming.
Question 2 of 5

What does if self.countries: check, given that self.countries is a list?

In Python, empty collections (list, dict, string) are falsy, and non-empty ones are truthy. if self.countries: is idiomatic for "does this list have anything in it?" โ€” equivalent to but more idiomatic than checking length explicitly.
Question 3 of 5

Given label = "Africa" if country.region == "Africa" else "Other", what kind of expression is this?

Python's ternary form is value_if_true if condition else value_if_false. It is an expression (it produces a value) rather than a statement, useful for short either/or assignments.
Question 4 of 5

A method find_by_region(region) returns [] when no country matches. Is returning an empty list the same kind of situation as raising an exception?

A search finding zero matches is a completely normal, expected outcome โ€” not an error. Reserve exceptions (Session 11) for truly invalid states, like a negative population. Conflating "not found" with "broken" makes calling code harder to write correctly.
Question 5 of 5

Given a method that must show a message when there are no countries, which is more idiomatic Python?

if not self.countries: relies on the truthy/falsy behaviour from this session and is the idiomatic Python style. The other two work but are unnecessarily verbose for the same check.

9. Reflection Questions

Think through these after the post-quiz. No right answer โ€” they are for discussion.

  1. Step 5 printed "1 countries loaded." โ€” grammatically wrong. How would you fix this with a ternary expression? Try writing it.
  2. Why is returning an empty list from find_by_region a better design than returning None when nothing is found?
  3. Can you think of a case in this lab where using an exception (Session 11) WOULD have been the right choice instead of a normal empty return?
  4. How does the truthy/falsy behaviour of empty lists connect to what you learned about mutability and identity in Session 05 and 02?

10. What Breaks If This Knowledge Is Missing?

  • None-checking chaos: If find_by_region sometimes returned None instead of an empty list, every single caller would need an extra None-check before it could safely loop over the result โ€” multiplying defensive code throughout the whole project for no benefit.
  • Building lists of objects (Session 18): The next session focuses on constructing many Country instances at once, often from raw data that may have missing fields. This session's conditional patterns are what keep that construction from crashing on incomplete records.
  • UI-equivalent states (parallel to the original React course): This is directly analogous to Session 17 of the source React course ("Conditional Rendering") โ€” handling an empty search result gracefully is the same problem whether you are printing to a console or rendering to a screen.

11. What We Learned

Python concept mastered: Conditional logic inside methods โ€” truthy/falsy checks on collections, ternary expressions, and treating "not found" as a normal outcome rather than an error.

Unlocks: CountryExplorer now handles the messy, incomplete cases real data throws at it, without crashing or producing misleading results.

Next session: Session 18 โ€” Building Lists of Objects. We build lists of Country objects from raw data at scale, and formalize the pattern for converting many dicts into many instances.