No history yet

Advanced Control Flow

More Than Just Loops

You already know how to build a list using a for loop and the .append() method. It works, but it can be a bit verbose. Python offers a more elegant and often faster way to do the same thing: comprehensions.

A list comprehension lets you build a new list by applying an expression to each item in an iterable. It's a single, readable line of code that replaces three or four lines of a standard loop.

logs = [
    {'level': 'INFO', 'message': 'User logged in'},
    {'level': 'DEBUG', 'message': 'Processing request #123'},
    {'level': 'WARNING', 'message': 'Disk space is low'},
    {'level': 'INFO', 'message': 'Data saved successfully'},
    {'level': 'ERROR', 'message': 'Database connection failed'}
]

# The old way with a for loop
info_messages = []
for log in logs:
    if log['level'] == 'INFO':
        info_messages.append(log['message'])

# The Pythonic way with a list comprehension
info_messages_comp = [log['message'] for log in logs if log['level'] == 'INFO']

print(info_messages_comp)
# Output: ['User logged in', 'Data saved successfully']

The structure is straightforward: [expression for item in iterable if condition]. The if condition part is optional but powerful for filtering. This pattern isn't just for lists; it extends to other data structures as well.

Comprehensions for Everything

Once you get the hang of list comprehensions, you can easily apply the same logic to create dictionaries and sets.

Dictionary comprehensions are perfect for creating a new dictionary from an iterable. You just need to provide a key-value pair.

Set comprehensions are similar, but they create a set, automatically handling uniqueness for you. This is great for when you want to find all the unique values in a dataset.

# Get unique log levels using a set comprehension
unique_levels = {log['level'] for log in logs}
print(unique_levels)
# Output: {'ERROR', 'INFO', 'WARNING', 'DEBUG'}

# Create a dictionary of error/warning messages
critical_logs = {log['level']: log['message'] for log in logs if log['level'] in ('ERROR', 'WARNING')}
print(critical_logs)
# Output: {'WARNING': 'Disk space is low', 'ERROR': 'Database connection failed'}

But what if you're working with a massive dataset, like millions of log entries? Creating a full list in memory might not be efficient. For this, Python gives us generator expressions.

A generator expression looks almost identical to a list comprehension, but it uses parentheses () instead of square brackets []. Instead of building a whole list at once, it creates a generator object. This object yields one item at a time, only when you ask for it. This approach is called "lazy evaluation," and it's incredibly memory-efficient.

import sys

# List comprehension (eager evaluation)
list_comp = [i for i in range(10000)]
print(f"List comprehension size: {sys.getsizeof(list_comp)} bytes")

# Generator expression (lazy evaluation)
gen_exp = (i for i in range(10000))
print(f"Generator expression size: {sys.getsizeof(gen_exp)} bytes")

# You can still iterate over the generator just like a list
# for num in gen_exp:
#    print(num) # This would print numbers 0 to 9999

Use a list comprehension when you need to have the entire list available, for example to sort it or get its length. Use a generator expression when you plan to iterate over the results once and the dataset is large.

New Tools for Old Problems

Python is always evolving, adding new syntax to make code cleaner and more powerful. Two recent additions that significantly improve control flow are the "walrus operator" and structural pattern matching.

The walrus operator :=, introduced in Python 3.8, allows you to assign a value to a variable as part of a larger expression. This is useful in loops where you need to get a value and then check if it's valid before using it.

# Before: A common pattern reading from a file
while True:
    line = f.readline()
    if not line:
        break
    # process(line)

# After: Using the walrus operator is more concise
while line := f.readline():
    # process(line)

Python 3.10 introduced structural pattern matching with the match and case statements. Think of it as a supercharged if/elif/else that can also check the structure of your data and bind variables.

It's perfect for handling data with a consistent but varied structure, like our log entries.

def process_log(log):
    match log:
        case {'level': 'INFO', 'message': msg}:
            print(f"Info: {msg}")
        case {'level': 'ERROR', 'message': msg} if 'database' in msg:
            print(f"CRITICAL DATABASE ERROR: {msg}")
        case {'level': 'ERROR'}:
            print("An unknown error occurred.")
        case _:
            # The underscore is a wildcard, matches anything
            print("Non-critical log entry.")

process_log({'level': 'ERROR', 'message': 'Database connection failed'})
# Output: CRITICAL DATABASE ERROR: Database connection failed

Notice how match can destructure the dictionary, bind msg to the value of the 'message' key, and even include conditional guards like if 'database' in msg.

What is Truth?

In Python, control flow doesn't always need an explicit True or False. Many objects have a "truth value." Any object can be tested for truthiness and used in an if or while condition.

The following are considered False:

  • None and False
  • Zero of any numeric type (e.g., 0, 0.0)
  • Any empty sequence or collection (e.g., '', (), [], {}, set())

Everything else is considered True. This allows for clean, readable code. Instead of checking the length of a list, you can just check the list itself.

# Verbose check
if len(my_list) > 0:
    print("List is not empty.")

# Pythonic 'truthiness' check
if my_list:
    print("List is not empty.")

Using Python's built-in truthiness testing makes your conditional logic more concise and idiomatic.

Ready to test your knowledge?

Quiz Questions 1/6

Which list comprehension is equivalent to the following code?

squares = []
for x in range(10):
    if x % 2 == 0:
        squares.append(x**2)
Quiz Questions 2/6

What is the primary advantage of using a generator expression (x for x in iterable) over a list comprehension [x for x in iterable]?

Mastering these advanced control flow patterns will make your code more expressive, efficient, and enjoyable to write.