Python Syntactic Sugar Cheatsheet
🟢 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
- Start Simple: Use basic comprehensions before trying advanced features
- Readability First: If a one-liner is hard to understand, use multiple lines
- Memory Awareness: Use generators for large datasets
- F-strings Everywhere: Replace old
.format()and%formatting - Context Managers: Always use
withfor files and resources - Meaningful Names: Even in comprehensions, use clear variable names
Quick Reference by Use Case
| I Want To… | Use This Pattern | Example |
|---|---|---|
| Transform a list | List comprehension | [x*2 for x in numbers] |
| Filter a list | List comprehension with if | [x for x in numbers if x > 0] |
| Build a dictionary | Dict comprehension | {k: v*2 for k, v in data.items()} |
| Format strings | F-strings | f"Hello {name}" |
| Process pairs | zip() | for a, b in zip(list1, list2): |
| Get index + value | enumerate() | for i, val in enumerate(items): |
| Handle large data | Generator expression | (x*2 for x in huge_list) |
| Open files safely | with statement | with open(file) as f: |
| Avoid repeated calculations | Walrus operator | if (n := len(data)) > 10: |
Remember: The best code is readable code. Use these patterns to make your code clearer, not more complex!