🟢 Core Patterns

List Comprehensions

When to use: Transform or filter a list of items

Basic Pattern:

[expression for item in list]

Simple Examples:

# Transform each number
numbers = [1, 2, 3, 4, 5]
squares = [x * x for x in numbers]
# Result: [1, 4, 9, 16, 25]

# Filter with condition
even_numbers = [x for x in numbers if x % 2 == 0]
# Result: [2, 4]

# Instead of this loop:
result = []
for x in numbers:
    if x % 2 == 0:
        result.append(x * x)

# Write this:
result = [x * x for x in numbers if x % 2 == 0]

Dictionary Comprehensions

When to use: Build dictionaries from existing data

Basic Pattern:

{key: value for item in list}

Simple Examples:

# Create word lengths dictionary
words = ['cat', 'dog', 'elephant']
lengths = {word: len(word) for word in words}
# Result: {'cat': 3, 'dog': 3, 'elephant': 8}

# Transform values in existing dictionary
prices = {'apple': 1.20, 'banana': 0.80}
rounded = {fruit: round(price) for fruit, price in prices.items()}
# Result: {'apple': 1, 'banana': 1}

F-strings (Python 3.6+)

When to use: Insert variables into strings (better than .format() or %)

Basic Pattern:

f"Text {variable} more text"

Simple Examples:

name = "Alice"
age = 25

# Basic variable insertion
message = f"Hello {name}!"
# Result: "Hello Alice!"

# Multiple variables
info = f"{name} is {age} years old"
# Result: "Alice is 25 years old"

# Simple expressions
status = f"Adult: {age >= 18}"
# Result: "Adult: True"

# Number formatting
price = 19.99
formatted = f"Price: ${price:.2f}"
# Result: "Price: $19.99"

Basic Unpacking

When to use: Get multiple values from lists/tuples at once

Simple Examples:

# Split coordinates
point = [10, 20]
x, y = point
# x = 10, y = 20

# Swap variables (no temp variable needed!)
a, b = 5, 3
a, b = b, a  # Now a = 3, b = 5

# Get function results
def get_name_age():
    return "Alice", 25

name, age = get_name_age()

Basic zip() and enumerate()

When to use: Combine lists or get index+value pairs

zip() - Combine lists:

names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]

# Process pairs together
for name, age in zip(names, ages):
    print(f"{name}: {age}")

# Create dictionary from two lists
people = dict(zip(names, ages))
# Result: {'Alice': 25, 'Bob': 30, 'Charlie': 35}

enumerate() - Get index and value:

items = ['apple', 'banana', 'cherry']

# Get position and item
for i, item in enumerate(items):
    print(f"{i}: {item}")
# Output: 0: apple, 1: banana, 2: cherry

# Start counting from 1
for i, item in enumerate(items, 1):
    print(f"{i}. {item}")
# Output: 1. apple, 2. banana, 3. cherry

Simple Conditional Expressions

When to use: Choose between two values based on a condition

Basic Pattern:

value_if_true if condition else value_if_false

Simple Examples:

age = 20
status = "adult" if age >= 18 else "minor"

# In lists
numbers = [1, -2, 3, -4, 5]
absolute = [x if x >= 0 else -x for x in numbers]
# Result: [1, 2, 3, 4, 5]

# Better than if/else for simple cases
# Instead of:
if score >= 60:
    grade = "Pass"
else:
    grade = "Fail"

# Write:
grade = "Pass" if score >= 60 else "Fail"

🟡 Common Patterns (Useful in Most Projects)

Generator Expressions

When to use: Process large amounts of data without storing everything in memory

Basic Pattern:

(expression for item in list)

Key Difference: Uses () instead of [] - creates items one at a time

# For large data sets - saves memory
large_numbers = range(1000000)

# List comprehension - stores all million numbers
squares_list = [x * x for x in large_numbers]  # Uses lots of memory

# Generator expression - creates one at a time
squares_gen = (x * x for x in large_numbers)   # Uses little memory

# Use generators with functions that process items one by one
total = sum(x * x for x in large_numbers)
max_value = max(x * x for x in range(100))

# Process large files efficiently
def process_large_file(filename):
    with open(filename) as f:
        # Generator - doesn't load entire file into memory
        lines = (line.strip().upper() for line in f)
        return sum(1 for line in lines if 'ERROR' in line)

Set Comprehensions

When to use: Get unique values or remove duplicates while transforming

Basic Pattern:

{expression for item in list}
# Get unique lengths
words = ['cat', 'dog', 'cat', 'elephant', 'dog']
unique_lengths = {len(word) for word in words}
# Result: {3, 8} - only unique values

# Remove duplicates while transforming
numbers = [1, 2, 2, 3, 3, 4]
unique_squares = {x * x for x in numbers}
# Result: {1, 4, 9, 16}

Advanced Unpacking with *

When to use: Handle lists of unknown length or skip unwanted values

# Get first, last, and everything in between
scores = [95, 87, 92, 78, 88, 91]
first, *middle, last = scores
# first = 95, middle = [87, 92, 78, 88], last = 91

# Skip values you don't need
data = ['header', 'value1', 'value2', 'value3', 'footer']
header, *_, footer = data
# header = 'header', footer = 'footer' (_ means "ignore these")

# Function with flexible arguments
def greet(name, *hobbies):
    print(f"Hi {name}!")
    if hobbies:
        print(f"I see you like: {', '.join(hobbies)}")

greet("Alice", "reading", "hiking", "coding")

# Unpack when calling functions
coordinates = [10, 20, 30]
print(*coordinates)  # Same as print(10, 20, 30)

The with Statement (Context Managers)

When to use: Work with files, databases, or anything that needs cleanup

Why it’s better: Automatically handles closing/cleanup even if errors occur

# File handling - file automatically closes
with open('data.txt') as file:
    content = file.read()
# File is guaranteed to be closed here, even if an error occurred

# Multiple files at once
with open('input.txt') as infile, open('output.txt', 'w') as outfile:
    data = infile.read()
    outfile.write(data.upper())
# Both files automatically closed

# Instead of this error-prone code:
file = open('data.txt')
try:
    content = file.read()
finally:
    file.close()  # Might forget this!

# Use this:
with open('data.txt') as file:
    content = file.read()

Walrus Operator := (Python 3.8+)

When to use: Assign and use a value in the same line (avoid repeating expensive operations)

Basic Pattern:

if (variable := expression):
    # use variable
# Avoid calling expensive function twice
# Instead of:
if len(expensive_computation()) > 10:
    print(f"Got {len(expensive_computation())} items")  # Called twice!

# Write:
if (n := len(expensive_computation())) > 10:
    print(f"Got {n} items")  # Called only once

# Useful in while loops
numbers = []
while (user_input := input("Enter number (or 'done'): ")) != "done":
    numbers.append(int(user_input))

# Check and use in one line
import re
text = "Phone: 123-456-7890"
if (match := re.search(r'\d{3}-\d{3}-\d{4}', text)):
    phone = match.group()
    print(f"Found phone: {phone}")

Slice Notation

When to use: Get parts of lists, strings, or other sequences

data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Basic slicing
first_three = data[:3]        # [0, 1, 2]
last_three = data[-3:]       # [7, 8, 9]
middle = data[2:7]           # [2, 3, 4, 5, 6]

# Skip elements
every_second = data[::2]     # [0, 2, 4, 6, 8]
every_third = data[::3]      # [0, 3, 6, 9]

# Reverse
reversed_data = data[::-1]   # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# Copy a list (shallow copy)
copy = data[:]               # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Remove first and last
without_ends = data[1:-1]    # [1, 2, 3, 4, 5, 6, 7, 8]

🔴 Advanced & Less Common

Pattern Matching (Python 3.10+)

When to use: Complex conditional logic that’s clearer than many if/elif statements

Why it’s useful: More readable than long if/elif chains, especially for structured data

# Instead of many if/elif statements
def handle_user_data(data):
    match data:
        # Match exact values
        case {"status": "active", "role": "admin"}:
            return "Admin user is active"
        
        # Match with variables (capture values)
        case {"status": "active", "role": role}:
            return f"Active {role} user"
        
        # Match with conditions
        case {"age": age} if age < 18:
            return "Minor user"
        
        # Match lists/tuples
        case [first, *rest] if len(rest) > 5:
            return f"Long list starting with {first}"
        
        # Default case
        case _:
            return "Unknown user type"

# Practical example - processing API responses
def process_api_response(response):
    match response:
        case {"error": {"code": 404}}:
            return "Not found"
        case {"error": {"code": code, "message": msg}}:
            return f"Error {code}: {msg}"
        case {"data": data, "count": count} if count > 0:
            return f"Success: {count} items"
        case {"data": []}:
            return "No data available"
        case _:
            return "Unexpected response format"

Advanced Built-in Functions

Modern alternatives to older patterns:

# Prefer comprehensions over map/filter
numbers = [1, 2, 3, 4, 5]

# Old style (still works, but less readable)
squared = list(map(lambda x: x**2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))

# Modern style (preferred)
squared = [x**2 for x in numbers]
evens = [x for x in numbers if x % 2 == 0]

# Complex example
words = ['hello', 'world', 'python', 'is', 'awesome']

# Old style - hard to read
result = list(map(str.upper, filter(lambda w: len(w) > 4, words)))

# Modern style - clear and readable
result = [word.upper() for word in words if len(word) > 4]
# Result: ['HELLO', 'WORLD', 'PYTHON', 'AWESOME']

Custom Context Managers

When to use: Create reusable patterns for setup/cleanup operations

Simple custom context manager using a class:

import time

class Timer:
    """Context manager to time operations"""
    def __enter__(self):
        self.start = time.time()
        return self
    
    def __exit__(self, *args):
        self.end = time.time()
        print(f"Operation took {self.end - self.start:.2f} seconds")

# Usage
with Timer():
    # Some slow operation
    time.sleep(1)
    result = sum(range(1000000))
# Automatically prints timing when done

# Using contextlib (simpler for basic cases)
from contextlib import contextmanager

@contextmanager
def temporary_setting(setting_name, temp_value):
    """Temporarily change a setting, then restore it"""
    old_value = get_setting(setting_name)
    set_setting(setting_name, temp_value)
    try:
        yield old_value
    finally:
        set_setting(setting_name, old_value)

# Usage
with temporary_setting('debug_mode', True):
    # Debug mode is temporarily enabled
    run_tests()
# Debug mode automatically restored to previous value

Common Mistakes to Avoid

1. Generator Exhaustion

# Problem: Generators can only be used once
gen = (x * 2 for x in range(5))
list1 = list(gen)  # [0, 2, 4, 6, 8]
list2 = list(gen)  # [] - Empty! Generator is exhausted

# Solution: Use a list if you need to reuse data
data = [x * 2 for x in range(5)]  # Can use multiple times

2. Mutable Default Arguments

# Problem: Default list gets modified
def add_item(item, my_list=[]):  # DON'T DO THIS
    my_list.append(item)
    return my_list

list1 = add_item("apple")     # ["apple"]
list2 = add_item("banana")    # ["apple", "banana"] - Oops!

# Solution: Use None as default
def add_item(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

3. Overly Complex Comprehensions

# Too complex - hard to read
result = [x.strip().upper() for sublist in data 
          for x in sublist if x and len(x) > 3 
          if not x.startswith('#')]

# Better - use regular loops for complex logic
result = []
for sublist in data:
    for x in sublist:
        if x and len(x) > 3 and not x.startswith('#'):
            result.append(x.strip().upper())

Best Practices

  1. Start Simple: Use basic comprehensions before trying advanced features
  2. Readability First: If a one-liner is hard to understand, use multiple lines
  3. Memory Awareness: Use generators for large datasets
  4. F-strings Everywhere: Replace old .format() and % formatting
  5. Context Managers: Always use with for files and resources
  6. Meaningful Names: Even in comprehensions, use clear variable names

Quick Reference by Use Case

I Want To…Use This PatternExample
Transform a listList comprehension[x*2 for x in numbers]
Filter a listList comprehension with if[x for x in numbers if x > 0]
Build a dictionaryDict comprehension{k: v*2 for k, v in data.items()}
Format stringsF-stringsf"Hello {name}"
Process pairszip()for a, b in zip(list1, list2):
Get index + valueenumerate()for i, val in enumerate(items):
Handle large dataGenerator expression(x*2 for x in huge_list)
Open files safelywith statementwith open(file) as f:
Avoid repeated calculationsWalrus operatorif (n := len(data)) > 10:

Remember: The best code is readable code. Use these patterns to make your code clearer, not more complex!