Introduction to Loop Control
Welcome to our exploration of loop control statements in Python! While loops give us the power to repeat code, loop control statements give us the finesse to decide exactly when and how those repetitions occur.
Think of loops as vehicles traveling along a path. Without control mechanisms, they would simply follow the route from beginning to end the specified number of times. Loop control statements are like the steering wheel, brakes, and accelerator that let you navigate more precisely—taking detours, skipping sections, or stopping the journey entirely when needed.
The code for this lesson can be found in the /week2/day2/loop_control.py file in your course repository.
Loop Control Mechanisms in Python
Python provides three primary mechanisms for controlling the flow of loops:
break: Terminates the loop completelycontinue: Skips the current iteration and moves to the next onepass: Does nothing but serves as a placeholder
These control mechanisms give you precise control over how your loops execute, allowing you to create more efficient and readable code by avoiding unnecessary iterations or handling special cases elegantly.
The break Statement: Exiting Loops Early
The break statement immediately terminates the loop it's in, transferring control to the first statement after the loop. It's like an emergency exit that lets you escape from a loop when a certain condition is met.
Basic Syntax
for item in sequence:
# Some code
if condition:
break
# More code
# Execution continues here after break
Think of break as a fire alarm in a building—when triggered, everyone immediately leaves the building (the loop) and goes to the designated meeting point (the next statement after the loop).
Simple Example: Finding an Element
numbers = [4, 7, 2, 9, 1, 5]
target = 9
for num in numbers:
print(f"Checking {num}")
if num == target:
print(f"Found the target: {num}")
break
print("Not the target, continuing search...")
print("Search complete")
# Output:
# Checking 4
# Not the target, continuing search...
# Checking 7
# Not the target, continuing search...
# Checking 2
# Not the target, continuing search...
# Checking 9
# Found the target: 9
# Search complete
Notice how the loop stops as soon as the target is found, without checking the remaining elements. This makes the search more efficient.
Using break with while Loops
print("Enter 'quit' to exit")
while True: # Infinite loop
user_input = input("Enter a word: ")
if user_input.lower() == 'quit':
print("Exiting program...")
break
print(f"You entered: {user_input}")
print("Program ended")
This creates an infinite loop that keeps asking for input until the user enters 'quit'. The break statement provides a controlled way to exit the otherwise endless loop.
Real-World Example: Processing a Large File
def process_log_until_error(log_file_path):
"""Process a log file until an error entry is found."""
try:
with open(log_file_path, 'r') as file:
line_number = 0
for line in file:
line_number += 1
# Process the line
print(f"Processing line {line_number}: {line.strip()}")
# Check if the line contains an error
if "ERROR" in line:
print(f"Error found at line {line_number}. Stopping processing.")
break
# Normally we would do more processing here
print("Finished processing the log file.")
except FileNotFoundError:
print(f"Could not find the log file: {log_file_path}")
# Example usage
process_log_until_error("server.log")
In this example, we're processing a log file line by line but stop as soon as we find an error. This is useful when you're troubleshooting and only care about the first error that occurred.
Break in Nested Loops
An important characteristic of break is that it only exits the innermost loop that contains it. If you need to exit multiple levels of nested loops, you'll need additional techniques.
for i in range(3):
print(f"Outer loop: i = {i}")
for j in range(3):
print(f" Inner loop: j = {j}")
if i == 1 and j == 1:
print(" Breaking inner loop")
break
print("Outer loop continues")
# Output:
# Outer loop: i = 0
# Inner loop: j = 0
# Inner loop: j = 1
# Inner loop: j = 2
# Outer loop continues
# Outer loop: i = 1
# Inner loop: j = 0
# Inner loop: j = 1
# Breaking inner loop
# Outer loop continues
# Outer loop: i = 2
# Inner loop: j = 0
# Inner loop: j = 1
# Inner loop: j = 2
# Outer loop continues
Notice how the break only exits the inner loop, and the outer loop continues to its next iteration.
Breaking Out of Multiple Loops
If you need to exit multiple nested loops at once, one approach is to use a flag variable:
found = False # Flag to control outer loop
for i in range(5):
print(f"Outer loop: i = {i}")
for j in range(5):
print(f" Inner loop: j = {j}")
if i == 2 and j == 3:
print(" Target found! Breaking out of all loops.")
found = True
break
if found:
break
print("Both loops have exited")
Another approach is to encapsulate the nested loops in a function and use return to exit all loops at once:
def search_2d_matrix(matrix, target):
"""Search for target in a 2D matrix and return position if found."""
for i in range(len(matrix)):
for j in range(len(matrix[i])):
print(f"Checking position ({i}, {j}): {matrix[i][j]}")
if matrix[i][j] == target:
print(f"Found {target} at position ({i}, {j})")
return (i, j) # Exits the entire function, effectively breaking both loops
print(f"{target} not found in the matrix")
return None
# Example usage
matrix = [
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]
]
position = search_2d_matrix(matrix, 5)
print(f"Result: {position}")
The continue Statement: Skipping Iterations
The continue statement skips the rest of the current iteration and immediately jumps to the next iteration of the loop. It's like hitting the "skip" button when listening to a playlist—you bypass the current song without stopping the entire playlist.
Basic Syntax
for item in sequence:
# Some code
if condition:
continue
# This code is skipped when continue is executed
# Loop continues with the next iteration
Think of continue as a detour sign on a road—it redirects you away from one path (the rest of the current iteration) and back to the main road (the start of the next iteration).
Simple Example: Skipping Values
for num in range(1, 11):
if num % 2 == 0: # If number is even
continue
print(num)
# Output:
# 1
# 3
# 5
# 7
# 9
Here, the continue statement skips even numbers, so only odd numbers are printed.
Cleaning Data with continue
raw_data = ["apple", "", "banana", None, "cherry", "", "date"]
clean_data = []
for item in raw_data:
# Skip empty or None values
if not item:
print(f"Skipping invalid data: {item!r}")
continue
# Process valid data
processed_item = item.strip().capitalize()
clean_data.append(processed_item)
print(f"Processed: {processed_item}")
print(f"Clean data: {clean_data}")
# Output:
# Processed: Apple
# Skipping invalid data: ''
# Processed: Banana
# Skipping invalid data: None
# Processed: Cherry
# Skipping invalid data: ''
# Processed: Date
# Clean data: ['Apple', 'Banana', 'Cherry', 'Date']
In this example, continue helps us skip invalid data items without complicating our processing logic with nested conditionals.
Using continue with while Loops
count = 0
while count < 10:
count += 1
if count % 3 == 0:
print(f"Skipping {count} (divisible by 3)")
continue
print(f"Processing {count}")
# Output:
# Processing 1
# Processing 2
# Skipping 3 (divisible by 3)
# Processing 4
# Processing 5
# Skipping 6 (divisible by 3)
# Processing 7
# Processing 8
# Skipping 9 (divisible by 3)
# Processing 10
With while loops, it's especially important to ensure that the loop condition can eventually become False, even when using continue. In this example, we increment count before the continue statement to avoid an infinite loop.
Real-World Example: Web Scraping
def scrape_articles(urls):
"""
Scrape articles from a list of URLs, handling various edge cases.
"""
articles = []
for url in urls:
print(f"Processing: {url}")
# Skip invalid URLs
if not url.startswith('http'):
print(f"Skipping invalid URL: {url}")
continue
# Simulate fetching the content
try:
content = fetch_content(url) # This would be a real function in production
except Exception as e:
print(f"Error fetching {url}: {e}")
continue
# Skip if no content was retrieved
if not content:
print(f"No content found at {url}")
continue
# Process the content
article = {
'url': url,
'title': extract_title(content),
'text': extract_text(content),
'date': extract_date(content)
}
articles.append(article)
print(f"Successfully scraped article: {article['title']}")
return articles
# Simulate the helper functions for the example
def fetch_content(url):
# Simulate successful/failed fetches
if 'example.com' in url:
return f"Content from {url}"
return None
def extract_title(content):
return f"Title from {content}"
def extract_text(content):
return f"Text from {content}"
def extract_date(content):
return "2023-01-01"
# Example usage
urls = [
'http://example.com/article1',
'invalid-url',
'http://example.com/article2',
'http://broken-site.com/article'
]
articles = scrape_articles(urls)
In this web scraping example, continue allows us to gracefully skip URLs that are invalid, fail to fetch, or don't contain the expected content. This creates a more robust scraper that can handle real-world edge cases without crashing.
Combining break and continue
You can use both break and continue in the same loop for different conditions:
numbers = [1, -2, 3, -4, 0, 5, -6, 7, 8]
sum_positive = 0
for num in numbers:
# Break if we encounter zero
if num == 0:
print("Found zero! Stopping summation.")
break
# Skip negative numbers
if num < 0:
print(f"Skipping negative number: {num}")
continue
# Process positive numbers
sum_positive += num
print(f"Adding {num} to sum, current total: {sum_positive}")
print(f"Final sum of positive numbers: {sum_positive}")
# Output:
# Adding 1 to sum, current total: 1
# Skipping negative number: -2
# Adding 3 to sum, current total: 4
# Skipping negative number: -4
# Found zero! Stopping summation.
# Final sum of positive numbers: 4
This example shows how break and continue can work together: continue skips negative numbers, and break stops the entire calculation if zero is encountered.
The pass Statement: Doing Nothing
The pass statement is a null operation—it does nothing. Unlike break and continue, which affect the flow of a loop, pass simply acts as a placeholder when syntax requires a statement but you don't want to execute any code.
Basic Syntax
for item in sequence:
if condition:
pass # Does nothing
else:
# Do something
Think of pass as a "no parking" sign—it occupies a space without actually doing anything there.
Using pass as a Placeholder
# Function stub to be implemented later
def process_data(data):
pass # To be implemented
# Conditional placeholder
age = 25
if age < 18:
pass # Handle minors (TODO)
else:
print("Access granted for adult")
The pass statement allows you to create syntactically correct code blocks that you plan to implement later. It's especially useful during development when you're sketching out the structure of your program.
Empty Loops with pass
# Find all even numbers (but don't do anything with them yet)
for num in range(1, 10):
if num % 2 == 0:
pass # TODO: Collect even numbers
else:
print(f"{num} is odd")
Here, pass allows us to acknowledge even numbers without taking any action yet. This can be useful when you're prototyping and want to focus on one part of your logic first.
Using pass in Exception Handling
def safe_divide(a, b):
try:
result = a / b
return result
except ZeroDivisionError:
# Silently handle division by zero
pass
# Default return value if exception occurs
return None
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None
In this example, pass allows us to silently handle a division by zero without executing any specific code in the exception handler. This creates a "fail-soft" behavior where the function returns None instead of crashing.
Empty Class Definition
# Define a class with no attributes or methods yet
class EmptyPlaceholder:
pass
# Empty classes can still be instantiated
obj = EmptyPlaceholder()
When designing class hierarchies, pass allows you to define classes that don't yet have any implementation but will be filled in later.
Real-World Example: State Machine
def process_order(order, state):
"""
Process an order based on its current state.
Returns the new state after processing.
"""
if state == "new":
print(f"Processing new order: {order['id']}")
# Validate order details
if not order.get('items'):
return "error"
return "processing"
elif state == "processing":
print(f"Order {order['id']} is being processed")
# Check inventory and payment
if order.get('payment_status') != 'complete':
print(f"Payment pending for order {order['id']}")
return "payment_pending"
return "ready_to_ship"
elif state == "payment_pending":
# This state is handled by a different subsystem
pass
elif state == "ready_to_ship":
print(f"Order {order['id']} is ready to ship")
# Arrange shipping
return "shipped"
elif state == "shipped":
print(f"Order {order['id']} has been shipped")
# Nothing to do until delivery confirmation
pass
elif state == "error":
print(f"Order {order['id']} has errors that must be resolved manually")
# Log the error for manual review
pass
# Return the unchanged state if no transition occurred
return state
# Example usage
order = {
'id': '12345',
'items': ['product1', 'product2'],
'payment_status': 'complete'
}
current_state = "new"
while current_state != "shipped":
print(f"Current state: {current_state}")
current_state = process_order(order, current_state)
print(f"New state: {current_state}")
print("-" * 30)
# Prevent infinite loop in this example
if current_state == "error" or current_state == "payment_pending":
break
In this state machine example, pass serves as a placeholder for states where the current function doesn't need to take any action. This might be because the action is handled by another system or because that state is simply a waiting state with no processing required.
Common Patterns and Techniques
Now that we've explored each control statement individually, let's look at some common patterns and techniques that use these statements effectively.
Early Exit Pattern
The early exit pattern uses break or return to exit a loop as soon as a solution is found, avoiding unnecessary iterations.
def find_first_prime(numbers):
"""Find the first prime number in a list."""
for num in numbers:
# Skip non-positive numbers
if num <= 1:
continue
# Check if num is prime
is_prime = True
for i in range(2, int(num**0.5) + 1):
if num % i == 0:
is_prime = False
break
if is_prime:
return num # Early exit when we find a prime
return None # No prime number found
numbers = [4, 6, 8, 9, 11, 13, 15]
prime = find_first_prime(numbers)
print(f"First prime number: {prime}") # 11
This pattern is efficient because it stops searching as soon as it finds what it's looking for, rather than checking all elements.
Skip and Filter Pattern
The skip and filter pattern uses continue to bypass elements that don't meet specific criteria.
def analyze_sensor_data(readings):
"""
Analyze sensor readings, filtering out invalid values.
"""
valid_readings = []
sum_valid = 0
count_valid = 0
min_valid = float('inf')
max_valid = float('-inf')
for reading in readings:
# Skip None values
if reading is None:
print("Skipping None reading")
continue
# Skip negative values (impossibilities for this sensor)
if reading < 0:
print(f"Skipping invalid negative reading: {reading}")
continue
# Skip outliers (values outside the expected range)
if reading > 100:
print(f"Skipping outlier reading: {reading}")
continue
# Process valid readings
valid_readings.append(reading)
sum_valid += reading
count_valid += 1
min_valid = min(min_valid, reading)
max_valid = max(max_valid, reading)
# Calculate statistics if we have valid readings
if count_valid > 0:
avg_valid = sum_valid / count_valid
return {
'valid_readings': valid_readings,
'count': count_valid,
'min': min_valid,
'max': max_valid,
'average': avg_valid
}
else:
return {'error': 'No valid readings found'}
# Example usage
sensor_data = [23.5, None, 18.2, -3.7, 105.9, 62.1, None, 74.3]
result = analyze_sensor_data(sensor_data)
print(result)
This pattern allows us to gracefully handle and skip invalid data without cluttering our processing logic with nested conditionals.
State Processing Pattern
This pattern uses different loop control mechanisms based on the state or phase of processing.
def process_transaction_batch(transactions):
"""
Process a batch of financial transactions,
handling different states appropriately.
"""
successful = []
failed = []
skipped = []
# Initial safety check
if not transactions:
return {'error': 'No transactions provided'}
# Process each transaction
for tx in transactions:
print(f"Processing transaction {tx['id']}: ${tx['amount']}")
# Check transaction state and handle accordingly
if tx['state'] == 'completed':
print(f"Transaction {tx['id']} already completed, skipping")
skipped.append(tx)
continue
elif tx['state'] == 'pending':
# Process pending transaction
if tx['amount'] <= 0:
print(f"Invalid amount ${tx['amount']} for transaction {tx['id']}")
tx['state'] = 'failed'
tx['error'] = 'Invalid amount'
failed.append(tx)
continue
if tx['balance'] < tx['amount']:
print(f"Insufficient funds for transaction {tx['id']}")
tx['state'] = 'failed'
tx['error'] = 'Insufficient funds'
failed.append(tx)
continue
# Successfully process the transaction
print(f"Successfully processed transaction {tx['id']}")
tx['state'] = 'completed'
successful.append(tx)
elif tx['state'] == 'failed':
print(f"Transaction {tx['id']} previously failed, skipping")
skipped.append(tx)
continue
else:
print(f"Unknown state '{tx['state']}' for transaction {tx['id']}")
tx['state'] = 'failed'
tx['error'] = 'Unknown state'
failed.append(tx)
# Return the results of batch processing
return {
'successful': successful,
'failed': failed,
'skipped': skipped,
'total_processed': len(successful) + len(failed),
'total_successful': len(successful),
'total_failed': len(failed),
'total_skipped': len(skipped)
}
# Example transactions
transactions = [
{'id': 'TX001', 'amount': 50.0, 'balance': 100.0, 'state': 'pending'},
{'id': 'TX002', 'amount': 25.0, 'balance': 20.0, 'state': 'pending'},
{'id': 'TX003', 'amount': -10.0, 'balance': 500.0, 'state': 'pending'},
{'id': 'TX004', 'amount': 100.0, 'balance': 300.0, 'state': 'completed'},
{'id': 'TX005', 'amount': 75.0, 'balance': 200.0, 'state': 'pending'},
{'id': 'TX006', 'amount': 40.0, 'balance': 50.0, 'state': 'failed'}
]
result = process_transaction_batch(transactions)
print("\nBatch processing results:")
print(f"Successful: {result['total_successful']}")
print(f"Failed: {result['total_failed']}")
print(f"Skipped: {result['total_skipped']}")
This pattern demonstrates how continue can be used to skip transactions that don't need processing, making the code more readable by avoiding deeply nested conditionals.
Infinite Loop with break
This pattern creates an intentional infinite loop that continues until a specific condition triggers a break.
def simple_calculator():
"""A simple calculator that runs until the user chooses to exit."""
print("Simple Calculator")
print("Enter 'q' to quit at any time")
while True: # Intentional infinite loop
# Get first number
num1_input = input("\nEnter first number: ")
if num1_input.lower() == 'q':
break
try:
num1 = float(num1_input)
except ValueError:
print("Invalid number. Please try again.")
continue
# Get operation
operation = input("Enter operation (+, -, *, /): ")
if operation.lower() == 'q':
break
if operation not in ['+', '-', '*', '/']:
print("Invalid operation. Please try again.")
continue
# Get second number
num2_input = input("Enter second number: ")
if num2_input.lower() == 'q':
break
try:
num2 = float(num2_input)
except ValueError:
print("Invalid number. Please try again.")
continue
# Perform calculation
if operation == '+':
result = num1 + num2
elif operation == '-':
result = num1 - num2
elif operation == '*':
result = num1 * num2
elif operation == '/':
if num2 == 0:
print("Error: Division by zero")
continue
result = num1 / num2
print(f"Result: {num1} {operation} {num2} = {result}")
print("Calculator closed. Goodbye!")
# Example usage
# simple_calculator()
This pattern is useful for interactive programs that should continue running until the user explicitly chooses to exit.
Best Practices and Guidelines
Let's explore some best practices for using loop control statements effectively and responsibly.
When to Use break
- Early termination: When you've found what you're looking for and don't need to continue
- Error conditions: To exit a loop when an unrecoverable error occurs
- User termination: To exit an interactive loop when the user signals completion
- Performance optimization: To avoid unnecessary iterations once a condition is met
When to Use continue
- Filtering: To skip elements that don't meet your criteria
- Error handling: To skip problematic items and continue with others
- Conditional processing: To apply special logic only to specific items
- Avoiding nested conditionals: To make your code flatter and more readable
When to Use pass
- Placeholders: When you need a syntactically correct statement but aren't ready to implement it
- Empty blocks: When you need an empty
if,else, or exception handler - Abstract methods: In classes that define methods that will be implemented by subclasses
- No-op branches: When a branch of conditional logic intentionally does nothing
Common Pitfalls to Avoid
- Infinite loops: Ensure that loops using
continuecan eventually terminate - Unreachable code: Be aware that code after a
breakwithin the same block will never execute - Overusing break/continue: Sometimes restructuring your logic is clearer than using many control statements
- Confusing pass with continue:
passdoes nothing, whilecontinueskips to the next iteration - Breaking out of the wrong loop: Remember that
breakonly affects the innermost loop containing it
Balancing Readability and Efficiency
# Less readable approach with deeply nested conditionals
def process_items(items):
results = []
for item in items:
if item is not None:
if isinstance(item, (int, float)):
if item > 0:
if item < 100:
results.append(item * 2)
return results
# More readable approach with continue statements
def process_items_better(items):
results = []
for item in items:
if item is None:
continue
if not isinstance(item, (int, float)):
continue
if item <= 0:
continue
if item >= 100:
continue
results.append(item * 2)
return results
Using continue for filtering can create flatter, more readable code compared to deeply nested conditionals.
Practice Exercises
To solidify your understanding of loop control statements, try these exercises. Solutions will be reviewed in class.
-
Basic break: Write a function that finds the first occurrence of a target value in a list and returns its index. If the value is not found, return -1.
-
continue Usage: Write a function that returns a list of all prime numbers within a given range.
-
Infinite Loop with break: Create a simple number guessing game that continues until the user guesses the correct number or types 'quit'.
-
Data Cleaning: Write a function that processes a list of values, skipping None values and negatives, and returns the average of the valid values.
-
Nested Loops with break: Create a function that finds the first pair of numbers in a list that sum to a target value and returns the indices.
-
pass Usage: Create a basic class hierarchy with placeholder methods using
pass. -
Advanced Challenge: Implement a simplified text parser that processes a text file line by line, skipping comments (lines starting with #) and blank lines, and breaks if it encounters a line containing 'END_OF_DATA'.
Real-World Applications
Loop control statements are essential in many real-world programming scenarios. Here are some practical applications:
Data Processing Pipeline
def process_data_batch(data_batch, config):
"""
Process a batch of data records, handling various error conditions.
"""
results = []
errors = []
skipped = 0
if not data_batch:
return {"error": "Empty batch provided"}
for record in data_batch:
record_id = record.get('id', 'unknown')
print(f"Processing record {record_id}")
# Skip records missing required fields
required_fields = config.get('required_fields', [])
missing_fields = [field for field in required_fields if field not in record]
if missing_fields:
print(f"Record {record_id} missing required fields: {missing_fields}")
errors.append({
'id': record_id,
'error': 'Missing required fields',
'details': missing_fields
})
continue
# Skip records marked for exclusion
if record.get('status') == 'exclude':
print(f"Record {record_id} marked for exclusion, skipping")
skipped += 1
continue
# Skip records that exceed maximum values if configured
max_value = config.get('max_value')
if max_value is not None and record.get('value', 0) > max_value:
print(f"Record {record_id} exceeds maximum value ({record['value']} > {max_value})")
errors.append({
'id': record_id,
'error': 'Exceeds maximum value',
'details': {'value': record['value'], 'max': max_value}
})
continue
# Process the valid record
try:
processed_record = {
'id': record_id,
'normalized_value': normalize_value(record.get('value', 0), config),
'category': categorize_record(record, config),
'timestamp': record.get('timestamp', 'unknown'),
'metadata': extract_metadata(record, config)
}
results.append(processed_record)
print(f"Successfully processed record {record_id}")
# Stop processing if we've reached the limit
if config.get('limit') and len(results) >= config['limit']:
print(f"Reached processing limit of {config['limit']} records")
break
except Exception as e:
print(f"Error processing record {record_id}: {str(e)}")
errors.append({
'id': record_id,
'error': 'Processing error',
'details': str(e)
})
return {
'results': results,
'errors': errors,
'processed': len(results),
'error_count': len(errors),
'skipped': skipped,
'total': len(data_batch)
}
# Simulate helper functions
def normalize_value(value, config):
factor = config.get('normalization_factor', 1)
return value * factor
def categorize_record(record, config):
categories = config.get('categories', {})
value = record.get('value', 0)
for category, range_val in categories.items():
min_val, max_val = range_val
if min_val <= value <= max_val:
return category
return 'uncategorized'
def extract_metadata(record, config):
# Extract specified metadata fields
metadata_fields = config.get('metadata_fields', [])
return {field: record.get(field) for field in metadata_fields if field in record}
# Example usage
data = [
{'id': '001', 'value': 150, 'timestamp': '2023-01-01', 'source': 'system1', 'quality': 'high'},
{'id': '002', 'value': 75, 'timestamp': '2023-01-02', 'source': 'system2'},
{'id': '003', 'status': 'exclude', 'value': 200, 'timestamp': '2023-01-03'},
{'id': '004', 'value': 300, 'timestamp': '2023-01-04', 'source': 'system1', 'quality': 'medium'},
{'id': '005', 'value': 50, 'source': 'system3', 'quality': 'low'}, # Missing timestamp
]
config = {
'required_fields': ['value', 'timestamp'],
'max_value': 250,
'limit': 10,
'normalization_factor': 0.1,
'categories': {
'low': (0, 50),
'medium': (51, 150),
'high': (151, 250)
},
'metadata_fields': ['source', 'quality']
}
result = process_data_batch(data, config)
print("\nProcessing summary:")
print(f"Processed: {result['processed']}/{result['total']} records")
print(f"Errors: {result['error_count']}")
print(f"Skipped: {result['skipped']}")
print("\nProcessed records:")
for record in result['results']:
print(f"- {record['id']}: {record['normalized_value']} ({record['category']})")
print("\nErrors:")
for error in result['errors']:
print(f"- {error['id']}: {error['error']} - {error['details']}")
This example demonstrates a comprehensive data processing pipeline that uses continue to skip invalid records in multiple ways and break to stop processing when a configured limit is reached.
Web Crawler with Depth Limiting
def simple_web_crawler(start_url, max_depth=2, max_pages=10):
"""
A simple web crawler that visits links up to a maximum depth.
"""
visited = set()
to_visit = [(start_url, 0)] # (url, depth)
results = []
page_count = 0
while to_visit and page_count < max_pages:
url, depth = to_visit.pop(0)
# Skip if we've already visited this URL
if url in visited:
continue
print(f"Visiting {url} (depth: {depth})")
# Mark as visited
visited.add(url)
try:
# Simulate fetching the page
page_content = fetch_page(url)
page_count += 1
# Process the page
title = extract_title(page_content)
summary = generate_summary(page_content)
results.append({
'url': url,
'title': title,
'summary': summary,
'depth': depth
})
# Stop if we've reached max pages
if page_count >= max_pages:
print(f"Reached maximum number of pages ({max_pages})")
break
# Don't extract further links if we're at max depth
if depth >= max_depth:
continue
# Extract links and add to the queue
links = extract_links(page_content, url)
for link in links:
if link not in visited:
to_visit.append((link, depth + 1))
except Exception as e:
print(f"Error crawling {url}: {str(e)}")
return results
# Simulate web crawling functions
def fetch_page(url):
# Simulate fetching HTML content
if "error" in url:
raise Exception("Failed to fetch page")
return f"Page at {url} Content for {url}"
def extract_title(content):
# Simulate extracting the title
import re
match = re.search(r'(.*?) ', content)
return match.group(1) if match else "No title"
def generate_summary(content):
# Simulate generating a summary
return f"Summary of content: {content[50:100]}..."
def extract_links(content, base_url):
# Simulate extracting links
# In a real crawler, this would parse the HTML and find actual links
if "leaf" in base_url:
return [] # Leaf page, no links
# Generate some fake links
if "branch" in base_url:
return [f"{base_url}/page{i}" for i in range(1, 4)]
return [f"{base_url}/branch{i}" for i in range(1, 3)] + [f"{base_url}/leaf{i}" for i in range(1, 3)]
# Example usage
seed_url = "https://example.com"
crawl_results = simple_web_crawler(seed_url, max_depth=2, max_pages=8)
print("\nCrawl results:")
for result in crawl_results:
print(f"{result['title']} ({result['url']}) - Depth: {result['depth']}")
This web crawler example uses continue to skip already visited URLs and pages at maximum depth, and break to stop when reaching the maximum number of pages.
Conclusion
Loop control statements—break, continue, and pass—are powerful tools that give you precise control over how your loops execute. They allow you to create more efficient and readable code by avoiding unnecessary iterations, handling special cases elegantly, and creating clean placeholders for future implementation.
To summarize what we've learned:
breakexits a loop completely when a condition is met, making your code more efficient by avoiding unnecessary iterations.continueskips the current iteration and moves to the next one, allowing you to filter out certain elements or handle special cases without nesting multiple conditionals.passdoes nothing but serves as a placeholder when syntax requires a statement, enabling you to sketch out code structure before implementation.
These control mechanisms are not just syntactic sugar—they represent fundamental patterns in programming that you'll use throughout your Python journey. As you continue learning and building more complex programs, you'll find that mastering loop control makes your code more elegant, efficient, and maintainable.
In the next lessons, we'll build on these concepts as we explore more advanced flow control structures, functions, and error handling techniques.
For further exploration, I recommend reviewing the official Python documentation on control flow statements: https://docs.python.org/3/tutorial/controlflow.html