Python Conditional Statements

Week 2 Day 2: Control Flow Fundamentals

Introduction to Control Flow

Welcome to our deep dive into Python's conditional statements! Today, we'll explore how Python makes decisions—one of the most fundamental aspects of programming. Just as humans make countless decisions daily, our programs need to evaluate conditions and choose different paths based on those evaluations.

Imagine your program as a river flowing downstream. Conditional statements are like forks in that river, directing the flow of execution based on certain conditions. Without these decision points, our programs would simply execute linearly from top to bottom—limiting their usefulness for solving real-world problems.

The code for this lesson can be found in the /week2/day2/conditional_statements.py file in your course repository.

If Statements: The Basic Decision Maker

The if statement is Python's most basic decision-making tool. It evaluates a condition and executes a block of code only if that condition is True.

Think of an if statement as a bouncer at a club. It checks if you meet certain criteria (the condition), and only if you do, grants you entry (executes the code block).

Here's the basic syntax:

if condition:
    # Code to execute if condition is True
    statement1
    statement2
    # ... more statements

A few key points about Python's if statements:

Simple Example: Age Verification

age = 19

if age >= 18:
    print("You are an adult.")
    print("You can vote.")
    
# Output: 
# You are an adult.
# You can vote.

Real-World Application: Authentication System

username = "user123"
password = "securepass"

input_username = input("Enter username: ")
input_password = input("Enter password: ")

if input_username == username and input_password == password:
    print("Login successful!")
    print("Welcome to the system.")

In this example, if either the username OR password is incorrect, nothing is printed and the login fails silently. We'll improve this with else statements shortly.

If-Else: Handling Alternative Outcomes

The if-else statement extends our decision-making capabilities by providing an alternative path when our condition is False.

Imagine a fork in a road: the if condition decides which path to take, but with if-else, we're guaranteed to take one of the two paths. There's no possibility of standing still.

if condition:
    # Execute this block if condition is True
    statements_when_true
else:
    # Execute this block if condition is False
    statements_when_false

Example: Improved Age Verification

age = 15

if age >= 18:
    print("You are an adult.")
    print("You can vote.")
else:
    print("You are a minor.")
    print("You cannot vote yet.")
    
# Output:
# You are a minor.
# You cannot vote yet.

Real-World Application: Improved Authentication

username = "user123"
password = "securepass"

input_username = input("Enter username: ")
input_password = input("Enter password: ")

if input_username == username and input_password == password:
    print("Login successful!")
    print("Welcome to the system.")
else:
    print("Login failed.")
    print("Please check your username and password.")

Much better! Now our users get feedback whether they succeed or fail to login.

The Binary Nature of If-Else

Think of if-else as a binary switch—it's either on or off, yes or no, true or false. Every possible input falls into one of exactly two categories. This is perfect for binary decisions, but what about when we need to consider multiple possibilities?

If-Elif-Else: Handling Multiple Possibilities

The elif statement (short for "else if") allows us to check multiple conditions in sequence. It extends the binary nature of if-else to accommodate multiple pathways.

Think of it as a series of filters or checkpoints, where Python evaluates each condition in order until it finds one that's True. Once it finds a True condition, it executes that block and skips all the rest.

if condition1:
    # Execute if condition1 is True
    statements_for_condition1
elif condition2:
    # Execute if condition1 is False AND condition2 is True
    statements_for_condition2
elif condition3:
    # Execute if condition1 and condition2 are False AND condition3 is True
    statements_for_condition3
else:
    # Execute if all conditions are False
    statements_when_all_false

The else block is optional. If omitted and all conditions are False, nothing will execute.

Example: Grading System

score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
elif score >= 60:
    grade = "D"
else:
    grade = "F"

print(f"Your grade is: {grade}")
# Output: Your grade is: B

Note how we only need to specify the lower bound in each condition, because once a condition is satisfied, the subsequent conditions aren't checked. This is more efficient and readable than writing:

# Less elegant approach
if score >= 90:
    grade = "A"
elif score >= 80 and score < 90:
    grade = "B"
elif score >= 70 and score < 80:
    grade = "C"
# and so on...

Real-World Application: Multi-tier Pricing System

item_count = int(input("How many items are you purchasing? "))
base_price = 10.0

if item_count == 1:
    total = base_price
    print(f"Regular price: ${total:.2f}")
elif 2 <= item_count <= 5:
    total = item_count * base_price * 0.95  # 5% discount
    print(f"5% bulk discount applied: ${total:.2f}")
elif 6 <= item_count <= 10:
    total = item_count * base_price * 0.9  # 10% discount
    print(f"10% bulk discount applied: ${total:.2f}")
else:
    total = item_count * base_price * 0.8  # 20% discount
    print(f"20% bulk discount applied: ${total:.2f}")

Important Consideration: Order Matters!

With if-elif-else chains, the order of conditions can significantly impact behavior. Python evaluates from top to bottom and executes the first True condition it encounters.

Consider our grading example with the conditions reversed:

# Incorrect order
score = 85

if score >= 60:  # This will always trigger for passing scores
    grade = "D"
elif score >= 70:
    grade = "C"
elif score >= 80:
    grade = "B"
elif score >= 90:
    grade = "A"
else:
    grade = "F"

print(f"Your grade is: {grade}")
# Output: Your grade is: D (Incorrect!)

Since 85 satisfies the first condition (≥60), Python assigns "D" and skips all other checks—even though the score qualifies for a higher grade!

Comparison and Logical Operators

Conditional statements rely on expressions that evaluate to either True or False. Python provides several operators to form these expressions:

Comparison Operators

Logical Operators

Example: Combining Operators

age = 25
income = 45000

# Using AND - both conditions must be True
if age > 18 and income > 30000:
    print("You qualify for a basic credit card.")

# Using OR - at least one condition must be True
if age > 65 or income > 100000:
    print("You qualify for premium services.")

# Using NOT - inverts the condition
if not (age < 18):
    print("You are not a minor.")

Practical Application: Weather Clothing Advisor

temperature = float(input("What's the temperature outside? "))
is_raining = input("Is it raining? (yes/no) ").lower() == "yes"

if temperature > 80:
    if is_raining:
        print("It's hot and rainy. Wear light clothes and take an umbrella.")
    else:
        print("It's hot. Wear light clothes and consider a hat.")
elif temperature > 60:
    if is_raining:
        print("It's mild and rainy. Wear a light jacket and take an umbrella.")
    else:
        print("It's mild. A t-shirt should be fine.")
elif temperature > 40:
    if is_raining:
        print("It's cool and rainy. Wear a warm jacket and take an umbrella.")
    else:
        print("It's cool. Wear a light jacket.")
else:
    if is_raining:
        print("It's cold and rainy. Wear a heavy coat, hat, gloves, and take an umbrella.")
    else:
        print("It's cold. Wear a heavy coat, hat, and gloves.")

Nested Conditionals

Conditional statements can be nested within other conditional blocks, creating more complex decision trees. This is useful when a second decision depends on the outcome of the first.

Think of nested conditionals like a choose-your-own-adventure book, where each decision leads to a new set of choices.

if outer_condition:
    # This code runs if outer_condition is True
    
    if nested_condition:
        # This code runs if both outer_condition and nested_condition are True
    else:
        # This code runs if outer_condition is True but nested_condition is False
else:
    # This code runs if outer_condition is False

Example: Advanced Authentication System

username = "admin"
password = "adminpass"
is_admin = True
system_locked = False

input_username = input("Username: ")
input_password = input("Password: ")

if input_username == username and input_password == password:
    print("Authentication successful!")
    
    if is_admin:
        print("Admin access granted.")
        
        if system_locked:
            print("System is currently locked. Admin override? (yes/no)")
            override = input().lower()
            
            if override == "yes":
                print("Override accepted. System unlocked.")
                system_locked = False
            else:
                print("Override cancelled. System remains locked.")
        else:
            print("System is operational. Full access granted.")
    else:
        print("User access granted. Limited functionality available.")
else:
    print("Authentication failed. Please try again.")

While nested conditionals can be powerful, deep nesting can lead to confusing "arrow code" or "pyramid of doom." Consider refactoring deeply nested conditionals into separate functions or using early returns to flatten the structure.

Flattening Nested Conditionals

# Instead of this deeply nested structure:
if condition1:
    if condition2:
        if condition3:
            do_something()
        else:
            do_alternative()
    else:
        handle_condition2_false()
else:
    handle_condition1_false()

# Consider early returns or guard clauses:
if not condition1:
    handle_condition1_false()
    return  # Exit the function early

if not condition2:
    handle_condition2_false()
    return

if condition3:
    do_something()
else:
    do_alternative()

Truthiness in Python

In Python, conditional statements evaluate the "truthiness" of expressions. Beyond the boolean values True and False, other values can be interpreted as either truthy or falsy in boolean contexts.

Falsy Values in Python:

Everything else is considered truthy.

Example: Leveraging Truthiness in Conditionals

# Checking if a list is empty
items = []
if items:
    print("We have some items!")
else:
    print("The list is empty.")
    
# Checking for default values
name = input("Enter your name (or leave empty for 'Guest'): ")
if not name:  # True if name is an empty string
    name = "Guest"
print(f"Welcome, {name}!")

# Checking for None
result = some_function()
if result is None:  # Explicit check for None
    print("Function returned None.")
elif result:  # General truthiness check
    print("Function returned a truthy value.")
else:
    print("Function returned a falsy value (but not None).")

An important note: When checking for None specifically, use is None rather than == None. This checks for identity rather than equality, which is more appropriate for the singleton None object.

Real-World Application: Default Configuration

def process_order(items, shipping_address=None, payment_method=None, coupon_code=""):
    # Validate essential components
    if not items:
        return "Error: Cannot process an order with no items"
    
    if not shipping_address:
        return "Error: Shipping address is required"
        
    # Set default payment method if none provided
    if not payment_method:
        payment_method = "Credit Card"
        
    # Apply coupon if provided
    discount = 0
    if coupon_code:
        discount = calculate_discount(coupon_code)
        
    # Process the order...
    return "Order processed successfully"

Short-Circuit Evaluation

Python uses short-circuit evaluation for logical operators, which can be leveraged for more concise and efficient code.

This behavior enables elegant patterns like default values and guard clauses.

Example: Default Values with OR

# Traditional approach
name = input("Enter your name: ")
if not name:
    name = "Guest"
    
# Short-circuit approach
name = input("Enter your name: ") or "Guest"

# The above works because:
# - If the input is non-empty (truthy), it's used as the name
# - If the input is empty (falsy), "Guest" is used instead

Example: Early Returns with AND

# Traditional approach
def process_item(item):
    if item:
        # Process the item
        return True
    else:
        return False
        
# Short-circuit approach
def process_item(item):
    return bool(item) and process_item_internal(item)
    
# In this case, if item is falsy, the function returns False immediately
# without calling process_item_internal().

Conditional Expressions (The Ternary Operator)

Python offers a condensed one-line conditional expression, often called the "ternary operator" (though it's not technically an operator in Python):

value_if_true if condition else value_if_false

This compact form is perfect for simple conditional assignments.

Example: Basic Usage

# Traditional if-else
if age >= 18:
    status = "adult"
else:
    status = "minor"
    
# Ternary equivalent
status = "adult" if age >= 18 else "minor"

Example: Nested Ternary (Use with caution)

category = "child" if age < 13 else "teenager" if age < 18 else "adult"

While nested ternary expressions are possible, they can quickly become difficult to read. Use them sparingly and consider traditional if-elif-else blocks for more complex conditions.

Real-World Application: Message Formatting

item_count = 5
message = f"You have {item_count} item{'s' if item_count != 1 else ''} in your cart."

# With item_count = 5: "You have 5 items in your cart."
# With item_count = 1: "You have 1 item in your cart."

Switch-Case Alternative in Python

Unlike many programming languages, Python traditionally didn't have a switch-case statement. Instead, we used if-elif-else chains or dictionary-based dispatchers. However, Python 3.10 (released in 2021) introduced the match-case statement, which provides similar functionality.

Traditional Dictionary-Based Switch

def get_day_type(day):
    return {
        "Monday": "Weekday",
        "Tuesday": "Weekday",
        "Wednesday": "Weekday",
        "Thursday": "Weekday",
        "Friday": "Weekday",
        "Saturday": "Weekend",
        "Sunday": "Weekend"
    }.get(day, "Unknown")  # .get() allows us to specify a default value

day = "Wednesday"
day_type = get_day_type(day)
print(f"{day} is a {day_type}.")  # Output: Wednesday is a Weekday.

Python 3.10+ Match-Case

# Requires Python 3.10 or newer
def get_day_type(day):
    match day:
        case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
            return "Weekday"
        case "Saturday" | "Sunday":
            return "Weekend"
        case _:  # Default case (like 'default' in other languages)
            return "Unknown"

day = "Sunday"
day_type = get_day_type(day)
print(f"{day} is a {day_type}.")  # Output: Sunday is a Weekend.

Match-case offers more powerful pattern matching capabilities than traditional switch statements, including destructuring, guard clauses, and more.

Best Practices and Common Pitfalls

Best Practices

Common Pitfalls

Example: Refactoring for Readability

# Less readable
if age > 18 and income > 30000 and credit_score > 700 and not has_defaulted:
    approve_loan()

# More readable
is_adult = age > 18
has_sufficient_income = income > 30000
has_good_credit = credit_score > 700
has_clean_history = not has_defaulted

if is_adult and has_sufficient_income and has_good_credit and has_clean_history:
    approve_loan()

Practice Exercises

To solidify your understanding of conditional statements, try these exercises. Solutions will be reviewed in class.

  1. Basic If-Else: Write a program that asks for a user's age and prints whether they are eligible to vote (18 or older).

  2. If-Elif-Else Chain: Create a BMI calculator that categorizes the result as Underweight, Normal, Overweight, or Obese based on standard BMI ranges.

  3. Nested Conditionals: Write a program that determines whether a year is a leap year. (Hint: A leap year is divisible by 4, except for century years which must be divisible by 400).

  4. Logical Operators: Create a simple password validator that checks if a password:

    • Is at least 8 characters long
    • Contains at least one uppercase letter
    • Contains at least one digit

  5. Truthiness: Write a function that checks if an input string is a palindrome (reads the same forwards and backwards, ignoring case), using Python's truthiness to handle empty inputs.

  6. Ternary Expressions: Rewrite the following using a ternary expression:

    message = ""
    if number % 2 == 0:
        message = "Even"
    else:
        message = "Odd"
  7. Advanced Challenge: Create a rock, paper, scissors game where the user plays against the computer. Use conditional statements to determine the winner.

Real-World Applications

Conditional statements are foundational to virtually all programming tasks. Here are some real-world applications that rely heavily on conditionals:

Example: E-commerce Checkout Logic

def calculate_total(items, user_type, coupon_code=None, shipping_country="US"):
    subtotal = sum(item["price"] * item["quantity"] for item in items)
    
    # Apply user type discount
    if user_type == "premium":
        discount_rate = 0.10  # 10% discount for premium users
    elif user_type == "loyalty" and subtotal > 100:
        discount_rate = 0.05  # 5% for loyalty members on orders over $100
    else:
        discount_rate = 0
        
    # Apply additional coupon if valid
    additional_discount = 0
    if coupon_code:
        if coupon_code == "WELCOME20" and not user_has_used_code_before(user_id, coupon_code):
            additional_discount = min(20, subtotal * 0.20)  # $20 or 20%, whichever is less
        elif coupon_code == "FREESHIP":
            shipping_fee = 0  # Will override shipping calculation below
            
    # Calculate discounted subtotal
    discounted_subtotal = subtotal * (1 - discount_rate) - additional_discount
    
    # Calculate shipping
    if "shipping_fee" not in locals():  # If not already set by a coupon
        if shipping_country == "US":
            if discounted_subtotal >= 50 or user_type == "premium":
                shipping_fee = 0
            else:
                shipping_fee = 5.99
        else:
            if discounted_subtotal >= 100:
                shipping_fee = 9.99
            else:
                shipping_fee = 19.99
                
    # Calculate tax (simplified)
    if shipping_country == "US":
        tax_rate = 0.07  # 7% sales tax
    else:
        tax_rate = 0  # Assume tax is handled differently for international
        
    tax = discounted_subtotal * tax_rate
    
    # Final total
    total = discounted_subtotal + shipping_fee + tax
    
    return {
        "subtotal": subtotal,
        "discount": subtotal * discount_rate + additional_discount,
        "shipping": shipping_fee,
        "tax": tax,
        "total": total
    }

This example demonstrates how real-world business logic often involves multiple layers of conditional checks, from user status to coupon validation to shipping rules.

Conclusion and Next Steps

We've covered the essential aspects of conditional statements in Python, from basic if-else structures to more complex patterns and best practices. These tools give your programs the ability to make decisions and respond dynamically to different conditions.

Tomorrow, we'll build on this foundation by exploring loops in Python, which allow for repeated execution of code blocks. The combination of conditionals and loops will significantly expand your ability to solve complex problems through code.

Remember to practice these concepts by completing the exercises and experimenting with your own examples. The ability to write effective conditional logic is one of the most important skills you'll develop as a programmer.

For further exploration, I recommend reviewing the official Python documentation on control flow: https://docs.python.org/3/tutorial/controlflow.html