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:
- The condition must evaluate to a boolean value (True or False)
- The colon (:) is required and marks the beginning of the conditional block
- The indentation defines the scope of the conditional block (conventionally 4 spaces)
- If the condition is False, Python simply skips the entire block
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
==: Equal to!=: Not equal to>: Greater than<: Less than>=: Greater than or equal to<=: Less than or equal to
Logical Operators
and: True if both conditions are Trueor: True if at least one condition is Truenot: Inverts the boolean value (True becomes False, False becomes True)
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:
False: The boolean FalseNone: Python's null equivalent0: Zero in any numeric type (0, 0.0, 0j)"": Empty string[]: Empty list(): Empty tuple{}: Empty dictionaryset(): Empty set
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.
- For
and: If the left operand is False, Python knows the entire expression must be False regardless of the right operand, so it doesn't evaluate the right side at all. - For
or: If the left operand is True, Python knows the entire expression must be True regardless of the right operand, so it doesn't evaluate the right side.
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
- Clarity over cleverness: Prefer readable code over unnecessarily compact expressions.
- Use meaningful comparisons:
if is_valid:is better thanif is_valid == True: - Consider the default case: Always include an else clause when appropriate to handle unexpected inputs.
- Keep conditionals simple: Break complex conditions into separate variables with descriptive names.
- Avoid deeply nested conditionals: Consider refactoring or early returns to flatten the code.
Common Pitfalls
- Assignment vs. comparison: Using
=(assignment) instead of==(comparison) in conditions. - Order of conditions: Placing more specific conditions after more general ones in if-elif chains.
- Redundant boolean comparisons: Writing
if x == True:instead ofif x: - Forgetting that 0 and empty containers are falsy: This can lead to unexpected behavior when checking numeric values or collections.
- Incorrect indentation: Python uses indentation to define code blocks, so inconsistent spacing can cause syntax errors or logical bugs.
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.
-
Basic If-Else: Write a program that asks for a user's age and prints whether they are eligible to vote (18 or older).
-
If-Elif-Else Chain: Create a BMI calculator that categorizes the result as Underweight, Normal, Overweight, or Obese based on standard BMI ranges.
-
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).
-
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
-
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.
-
Ternary Expressions: Rewrite the following using a ternary expression:
message = "" if number % 2 == 0: message = "Even" else: message = "Odd" -
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:
- Form Validation: Checking if user inputs meet required criteria.
- Authentication Systems: Verifying credentials and authorization levels.
- E-commerce: Calculating discounts, shipping costs, and eligibility for promotions.
- Content Management: Displaying different content based on user roles or preferences.
- Data Processing: Filtering and categorizing data based on various criteria.
- Game Development: Making decisions based on player actions and game state.
- Financial Applications: Approving transactions based on account status and limits.
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