List Comprehensions & Lambda Functions

Write cleaner, more Pythonic code with elegant one-liners

Introduction

Python is famous for its elegant, readable syntax. List comprehensions and lambda functions are two powerful features that let you write more concise and expressive code. Instead of writing multi-line loops to transform data, you can often accomplish the same task in a single, readable line. These tools are fundamental to writing "Pythonic" code, the idiomatic style that makes Python code beautiful and efficient. Mastering them will make you more productive and your code more maintainable.

List Comprehensions

List comprehensions provide a concise way to create lists. They're faster and more readable than traditional for loops for creating lists from existing sequences.

Basic Syntax

The basic syntax is: [expression for item in iterable]

# Traditional approach with for loop
squares = []
for x in range(5):
    squares.append(x ** 2)
# squares = [0, 1, 4, 9, 16]

# List comprehension - cleaner and faster
squares = [x ** 2 for x in range(5)]
# [0, 1, 4, 9, 16]

# More examples
numbers = [1, 2, 3, 4, 5]
doubled = [n * 2 for n in numbers]
# [2, 4, 6, 8, 10]

words = ["hello", "world", "python"]
uppercase = [word.upper() for word in words]
# ["HELLO", "WORLD", "PYTHON"]

lengths = [len(word) for word in words]
# [5, 5, 6]

With Conditions (Filtering)

Add an if clause to filter items: [expression for item in iterable if condition]

# Filter even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [n for n in numbers if n % 2 == 0]
# [2, 4, 6, 8, 10]

# Filter odd numbers
odds = [n for n in numbers if n % 2 != 0]
# [1, 3, 5, 7, 9]

# Filter and transform
squared_evens = [n ** 2 for n in numbers if n % 2 == 0]
# [4, 16, 36, 64, 100]

# Filter strings by length
words = ["a", "ab", "abc", "abcd"]
long_words = [word for word in words if len(word) > 2]
# ["abc", "abcd"]

# Filter with multiple conditions
numbers = range(1, 21)
result = [n for n in numbers if n % 2 == 0 and n % 3 == 0]
# [6, 12, 18] (divisible by both 2 and 3)

If-Else in Comprehensions

Use conditional expressions for different transformations: [true_expr if condition else false_expr for item in iterable]

# Label numbers as even or odd
numbers = [1, 2, 3, 4, 5]
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ["odd", "even", "odd", "even", "odd"]

# Different transformations based on condition
numbers = [1, 2, 3, 4, 5]
result = [n * 2 if n % 2 == 0 else n * 3 for n in numbers]
# [3, 4, 9, 8, 15] (even * 2, odd * 3)

# Replace negative numbers with zero
numbers = [-2, 3, -1, 5, -4]
positive_only = [n if n > 0 else 0 for n in numbers]
# [0, 3, 0, 5, 0]

# Practical: classify scores
scores = [85, 92, 78, 95, 67]
grades = ["Pass" if score >= 70 else "Fail" for score in scores]
# ["Pass", "Pass", "Pass", "Pass", "Fail"]

Note the difference: When filtering (keeping some items), the if goes at the end. When using if-else (transforming all items differently), it goes before the for.

Nested Comprehensions

Process nested structures like matrices or create combinations.

# Flatten a 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Create combinations
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
combinations = [f"{color}-{size}" for color in colors for size in sizes]
# ["red-S", "red-M", "red-L", "blue-S", "blue-M", "blue-L"]

# Nested with condition
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
evens_only = [num for row in matrix for num in row if num % 2 == 0]
# [2, 4, 6, 8]

Practical Examples

# Extract file extensions
files = ["photo.jpg", "document.pdf", "script.py", "data.csv"]
extensions = [file.split(".")[-1] for file in files]
# ["jpg", "pdf", "py", "csv"]

# Parse CSV-like data
data = "Alice,30,Engineer|Bob,25,Designer|Charlie,35,Manager"
rows = data.split("|")
parsed = [row.split(",") for row in rows]
# [["Alice", "30", "Engineer"],
#  ["Bob", "25", "Designer"],
#  ["Charlie", "35", "Manager"]]

# Extract numbers from strings
texts = ["Order 123", "Item 456", "Code 789"]
numbers = [int(''.join(c for c in text if c.isdigit())) for text in texts]
# [123, 456, 789]

# Create coordinate pairs
coords = [(x, y) for x in range(3) for y in range(3)]
# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]

# Filter and extract email domains
emails = ["alice@gmail.com", "bob@yahoo.com", "charlie@gmail.com"]
gmail_users = [email.split("@")[0] for email in emails
               if email.endswith("@gmail.com")]
# ["alice", "charlie"]

Readability matters: While comprehensions are powerful, don't make them too complex. If a comprehension becomes hard to read, use a regular loop instead. Aim for clarity over cleverness!

Dictionary Comprehensions

Create dictionaries concisely using a similar syntax: {key: value for item in iterable}

Basic Dictionary Comprehensions

# Create dict from lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
people = {name: age for name, age in zip(names, ages)}
# {"Alice": 25, "Bob": 30, "Charlie": 35}

# Square numbers
squares = {x: x ** 2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Create dict from list
fruits = ["apple", "banana", "cherry"]
fruit_lengths = {fruit: len(fruit) for fruit in fruits}
# {"apple": 5, "banana": 6, "cherry": 6}

# Invert a dictionary
original = {"a": 1, "b": 2, "c": 3}
inverted = {value: key for key, value in original.items()}
# {1: "a", 2: "b", 3: "c"}

With Conditions

# Filter by condition
scores = {"Alice": 85, "Bob": 92, "Charlie": 78, "David": 95}
high_scores = {name: score for name, score in scores.items()
               if score >= 90}
# {"Bob": 92, "David": 95}

# Transform values conditionally
prices = {"apple": 1.2, "banana": 0.5, "cherry": 3.0}
discounted = {fruit: price * 0.9 if price > 1 else price
              for fruit, price in prices.items()}
# {"apple": 1.08, "banana": 0.5, "cherry": 2.7}

# Filter and transform keys
numbers = {1: "one", 2: "two", 3: "three", 4: "four"}
evens_upper = {k: v.upper() for k, v in numbers.items() if k % 2 == 0}
# {2: "TWO", 4: "FOUR"}

Practical Examples

# Count character frequency
text = "hello world"
freq = {char: text.count(char) for char in text if char != " "}
# {"h": 1, "e": 1, "l": 3, "o": 2, "w": 1, "r": 1, "d": 1}

# Group by length
words = ["a", "ab", "abc", "abcd", "ab", "a"]
by_length = {word: len(word) for word in words}
# {"a": 1, "ab": 2, "abc": 3, "abcd": 4}

# Parse query string
query = "name=Alice&age=30&city=NYC"
params = {pair.split("=")[0]: pair.split("=")[1]
          for pair in query.split("&")}
# {"name": "Alice", "age": "30", "city": "NYC"}

# Convert Celsius to Fahrenheit
celsius = {"morning": 20, "afternoon": 25, "evening": 18}
fahrenheit = {time: (temp * 9/5) + 32 for time, temp in celsius.items()}
# {"morning": 68.0, "afternoon": 77.0, "evening": 64.4}

# Create lookup table
users = [
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
    {"id": 3, "name": "Charlie"}
]
user_lookup = {user["id"]: user["name"] for user in users}
# {1: "Alice", 2: "Bob", 3: "Charlie"}

Set Comprehensions

Create sets (unique, unordered collections) using comprehension syntax: {expression for item in iterable}

Basic Set Comprehensions

# Remove duplicates automatically
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 5]
unique = {n for n in numbers}
# {1, 2, 3, 4, 5}

# Unique squared values
squares = {x ** 2 for x in [1, -1, 2, -2, 3, -3]}
# {1, 4, 9} (negatives produce same squares)

# Extract unique characters
text = "hello world"
chars = {c for c in text if c != " "}
# {"h", "e", "l", "o", "w", "r", "d"}

# Unique lengths
words = ["a", "ab", "abc", "ab", "a"]
lengths = {len(word) for word in words}
# {1, 2, 3}

Practical Examples

# Extract unique domains from emails
emails = [
    "alice@gmail.com",
    "bob@yahoo.com",
    "charlie@gmail.com",
    "david@hotmail.com"
]
domains = {email.split("@")[1] for email in emails}
# {"gmail.com", "yahoo.com", "hotmail.com"}

# Find unique tags (case-insensitive)
tags = ["Python", "python", "PYTHON", "JavaScript", "javascript"]
unique_tags = {tag.lower() for tag in tags}
# {"python", "javascript"}

# Extract unique file extensions
files = ["doc.pdf", "img.jpg", "data.csv", "photo.jpg", "report.pdf"]
extensions = {file.split(".")[-1] for file in files}
# {"pdf", "jpg", "csv"}

# Get unique first letters
names = ["Alice", "Bob", "Andrew", "Charlie", "Anna"]
initials = {name[0] for name in names}
# {"A", "B", "C"}

Lambda Functions

Lambda functions are small, anonymous functions defined with the lambda keyword. They're perfect for simple operations that you need to pass as arguments to other functions.

Lambda Syntax

Syntax: lambda arguments: expression

# Regular function
def add(x, y):
    return x + y

result = add(5, 3)  # 8

# Equivalent lambda function
add = lambda x, y: x + y
result = add(5, 3)  # 8

# Lambda with single argument
square = lambda x: x ** 2
square(5)  # 25

double = lambda x: x * 2
double(10)  # 20

# Lambda with multiple arguments
multiply = lambda x, y, z: x * y * z
multiply(2, 3, 4)  # 24

# Lambda without arguments (rare)
get_pi = lambda: 3.14159
get_pi()  # 3.14159

When to use lambdas: Lambda functions are best for simple, one-line operations. For complex logic, use regular functions with def for better readability.

Lambdas with Sorting

One of the most common uses of lambda functions is as a key function for sorting.

# Sort by length
words = ["python", "is", "awesome", "language"]
sorted_words = sorted(words, key=lambda w: len(w))
# ["is", "python", "awesome", "language"]

# Sort by second element
pairs = [(1, 5), (3, 2), (2, 8), (4, 1)]
sorted_pairs = sorted(pairs, key=lambda p: p[1])
# [(4, 1), (3, 2), (1, 5), (2, 8)]

# Sort dictionaries by value
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
sorted_scores = sorted(scores.items(), key=lambda item: item[1], reverse=True)
# [("Bob", 92), ("Alice", 85), ("Charlie", 78)]

# Sort by multiple criteria (length, then alphabetically)
words = ["cat", "dog", "elephant", "ant", "zebra"]
sorted_words = sorted(words, key=lambda w: (len(w), w))
# ["ant", "cat", "dog", "zebra", "elephant"]

# Sort case-insensitive
names = ["alice", "Bob", "CHARLIE", "david"]
sorted_names = sorted(names, key=lambda n: n.lower())
# ["alice", "Bob", "CHARLIE", "david"]

Lambdas in Data Processing

# Find max by custom criterion
people = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35}
]
oldest = max(people, key=lambda p: p["age"])
# {"name": "Charlie", "age": 35}

youngest = min(people, key=lambda p: p["age"])
# {"name": "Bob", "age": 25}

# Sort complex data
students = [
    {"name": "Alice", "grade": 85, "age": 20},
    {"name": "Bob", "grade": 92, "age": 19},
    {"name": "Charlie", "grade": 85, "age": 21}
]

# Sort by grade (descending), then by age (ascending)
sorted_students = sorted(
    students,
    key=lambda s: (-s["grade"], s["age"])
)
# Bob (92, 19), Alice (85, 20), Charlie (85, 21)

# Group by condition
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
from itertools import groupby
grouped = {k: list(g) for k, g in groupby(
    numbers,
    key=lambda x: "even" if x % 2 == 0 else "odd"
)}
# Note: groupby requires consecutive items, use sorted() first for general grouping

The map() Function

map() applies a function to every item in an iterable and returns an iterator with the results. It's often used with lambda functions.

Basic map() Usage

# Square all numbers
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
# [1, 4, 9, 16, 25]

# Convert to uppercase
words = ["hello", "world", "python"]
uppercase = list(map(lambda w: w.upper(), words))
# ["HELLO", "WORLD", "PYTHON"]

# Convert types
strings = ["1", "2", "3", "4", "5"]
integers = list(map(int, strings))
# [1, 2, 3, 4, 5]

prices = ["10.5", "20.99", "5.75"]
floats = list(map(float, prices))
# [10.5, 20.99, 5.75]

# Using regular function with map
def double(x):
    return x * 2

numbers = [1, 2, 3, 4, 5]
doubled = list(map(double, numbers))
# [2, 4, 6, 8, 10]

map() with Multiple Iterables

Pass multiple iterables to map(), the function receives one item from each iterable.

# Add corresponding elements
list1 = [1, 2, 3, 4]
list2 = [10, 20, 30, 40]
sums = list(map(lambda x, y: x + y, list1, list2))
# [11, 22, 33, 44]

# Multiply corresponding elements
products = list(map(lambda x, y: x * y, list1, list2))
# [10, 40, 90, 160]

# Combine strings
first_names = ["Alice", "Bob", "Charlie"]
last_names = ["Smith", "Jones", "Brown"]
full_names = list(map(lambda f, l: f + " " + l, first_names, last_names))
# ["Alice Smith", "Bob Jones", "Charlie Brown"]

# Multiple operations
prices = [10, 20, 30]
quantities = [2, 3, 1]
tax_rates = [0.1, 0.15, 0.08]
totals = list(map(
    lambda p, q, t: p * q * (1 + t),
    prices, quantities, tax_rates
))
# [22.0, 69.0, 32.4]

map() vs List Comprehension

numbers = [1, 2, 3, 4, 5]

# Using map()
squared_map = list(map(lambda x: x ** 2, numbers))
# [1, 4, 9, 16, 25]

# Using list comprehension (more Pythonic)
squared_comp = [x ** 2 for x in numbers]
# [1, 4, 9, 16, 25]

# map() is useful with existing functions
strings = ["1", "2", "3"]
ints_map = list(map(int, strings))  # Clean
ints_comp = [int(s) for s in strings]  # Also fine

# List comprehension is better for filtering
numbers = [1, 2, 3, 4, 5, 6]
# This is NOT possible with map alone
evens = [x ** 2 for x in numbers if x % 2 == 0]
# [4, 16, 36]

Pythonic choice: List comprehensions are generally preferred in modern Python because they're more readable. Use map()when you already have a function to apply, especially built-in functions like int, str, or len.

The filter() Function

filter() creates an iterator containing only the items for which a function returns True. It's used for filtering sequences based on conditions.

Basic filter() Usage

# Filter even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4, 6, 8, 10]

# Filter odd numbers
odds = list(filter(lambda x: x % 2 != 0, numbers))
# [1, 3, 5, 7, 9]

# Filter numbers greater than 5
greater_than_5 = list(filter(lambda x: x > 5, numbers))
# [6, 7, 8, 9, 10]

# Filter strings by length
words = ["a", "ab", "abc", "abcd"]
long_words = list(filter(lambda w: len(w) > 2, words))
# ["abc", "abcd"]

# Filter non-empty strings
strings = ["hello", "", "world", "", "python"]
non_empty = list(filter(lambda s: s != "", strings))
# ["hello", "world", "python"]

# Or simply use filter(None, ...) to remove falsy values
non_empty = list(filter(None, strings))
# ["hello", "world", "python"]

filter() with Complex Conditions

# Filter by multiple conditions
numbers = range(1, 21)
result = list(filter(lambda x: x % 2 == 0 and x % 3 == 0, numbers))
# [6, 12, 18] (divisible by both 2 and 3)

# Filter dictionaries
people = [
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30},
    {"name": "Charlie", "age": 35},
    {"name": "David", "age": 28}
]

adults = list(filter(lambda p: p["age"] >= 30, people))
# [{"name": "Bob", "age": 30}, {"name": "Charlie", "age": 35}]

# Filter valid emails
emails = [
    "alice@example.com",
    "invalid-email",
    "bob@test.com",
    "no-at-sign.com"
]
valid = list(filter(lambda e: "@" in e and "." in e.split("@")[1], emails))
# ["alice@example.com", "bob@test.com"]

# Filter by string methods
words = ["Python", "Java", "PYTHON", "python", "C++"]
python_variants = list(filter(lambda w: w.lower() == "python", words))
# ["Python", "PYTHON", "python"]

filter() vs List Comprehension

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Using filter()
evens_filter = list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4, 6, 8, 10]

# Using list comprehension (more Pythonic)
evens_comp = [x for x in numbers if x % 2 == 0]
# [2, 4, 6, 8, 10]

# List comprehension can filter AND transform
squared_evens = [x ** 2 for x in numbers if x % 2 == 0]
# [4, 16, 36, 64, 100]

# With filter(), you'd need map() too
squared_evens_filter = list(map(lambda x: x ** 2,
                                 filter(lambda x: x % 2 == 0, numbers)))
# [4, 16, 36, 64, 100] (less readable)

# filter() is good when you have an existing predicate function
def is_valid_email(email):
    return "@" in email and len(email) > 5

emails = ["a@b.c", "test@example.com", "invalid"]
valid = list(filter(is_valid_email, emails))
# ["test@example.com"]

Best practice: List comprehensions are usually more Pythonic for filtering. Use filter() when you have an existing predicate function or when working with infinite iterators.

The zip() Function

zip() combines multiple iterables element-wise, creating tuples of corresponding elements. It's extremely useful for parallel iteration.

Basic zip() Usage

# Combine two lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
combined = list(zip(names, ages))
# [("Alice", 25), ("Bob", 30), ("Charlie", 35)]

# Iterate over pairs
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")
# Alice is 25 years old
# Bob is 30 years old
# Charlie is 35 years old

# Combine three lists
first = ["Alice", "Bob", "Charlie"]
last = ["Smith", "Jones", "Brown"]
ages = [25, 30, 35]
people = list(zip(first, last, ages))
# [("Alice", "Smith", 25), ("Bob", "Jones", 30), ("Charlie", "Brown", 35)]

# Create dictionary from two lists
keys = ["name", "age", "city"]
values = ["Alice", 25, "NYC"]
person = dict(zip(keys, values))
# {"name": "Alice", "age": 25, "city": "NYC"}

zip() with Different Lengths

zip() stops when the shortest iterable is exhausted.

# Different lengths - stops at shortest
names = ["Alice", "Bob", "Charlie", "David"]
ages = [25, 30, 35]
combined = list(zip(names, ages))
# [("Alice", 25), ("Bob", 30), ("Charlie", 35)]
# "David" is ignored

# Use itertools.zip_longest for different behavior
from itertools import zip_longest

combined_long = list(zip_longest(names, ages, fillvalue="N/A"))
# [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("David", "N/A")]

# Empty iterable
result = list(zip([], [1, 2, 3]))
# [] (empty result)

Practical zip() Examples

# Calculate total price
items = ["apple", "banana", "cherry"]
prices = [0.5, 0.3, 1.2]
quantities = [4, 6, 2]

total = sum(p * q for p, q in zip(prices, quantities))
# 0.5*4 + 0.3*6 + 1.2*2 = 2.0 + 1.8 + 2.4 = 6.2

# Create detailed receipt
for item, price, qty in zip(items, prices, quantities):
    subtotal = price * qty
    print(f"{item:10} {qty}x ${price:.2f} = ${subtotal:.2f}")
# apple      4x $0.50 = $2.00
# banana     6x $0.30 = $1.80
# cherry     2x $1.20 = $2.40

# Transpose a matrix (swap rows and columns)
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
transposed = list(zip(*matrix))
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

# Convert back to list of lists
transposed = [list(row) for row in zip(*matrix)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

# Compare two lists element by element
list1 = [1, 2, 3, 4, 5]
list2 = [1, 2, 0, 4, 5]
differences = [(i, a, b) for i, (a, b) in enumerate(zip(list1, list2))
               if a != b]
# [(2, 3, 0)] (index 2 differs: 3 vs 0)

# Merge sorted sequences (simple version)
sorted1 = [1, 3, 5, 7]
sorted2 = [2, 4, 6, 8]
# This isn't a proper merge, just for illustration
pairs = list(zip(sorted1, sorted2))
# [(1, 2), (3, 4), (5, 6), (7, 8)]

Unzipping with zip()

Use zip() with the unpacking operator * to "unzip" a sequence of tuples.

# Zip creates pairs
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
pairs = list(zip(names, ages))
# [("Alice", 25), ("Bob", 30), ("Charlie", 35)]

# Unzip splits them back
names_back, ages_back = zip(*pairs)
# names_back = ("Alice", "Bob", "Charlie")
# ages_back = (25, 30, 35)

# Convert back to lists if needed
names_list = list(names_back)
ages_list = list(ages_back)

# Practical example: separate coordinates
points = [(1, 2), (3, 4), (5, 6)]
x_coords, y_coords = zip(*points)
# x_coords = (1, 3, 5)
# y_coords = (2, 4, 6)

# Extract columns from data
data = [
    ("Alice", 25, "Engineer"),
    ("Bob", 30, "Designer"),
    ("Charlie", 35, "Manager")
]
names, ages, titles = zip(*data)
# names = ("Alice", "Bob", "Charlie")
# ages = (25, 30, 35)
# titles = ("Engineer", "Designer", "Manager")

Combining Everything: Real-World Examples

Now let's see how these tools work together to solve practical problems elegantly.

Example 1: Data Processing Pipeline

# Process sales data
sales_data = [
    "Product A,10,25.50",
    "Product B,5,12.00",
    "Product C,15,8.75",
    "Product D,8,30.00"
]

# Parse and calculate totals
parsed = [line.split(",") for line in sales_data]
# [["Product A", "10", "25.50"], ...]

# Convert to proper types and calculate
totals = [
    {
        "product": name,
        "quantity": int(qty),
        "price": float(price),
        "total": int(qty) * float(price)
    }
    for name, qty, price in parsed
]

# Filter high-value sales (> $100)
high_value = list(filter(lambda x: x["total"] > 100, totals))
# [{"product": "Product A", "quantity": 10, "price": 25.5, "total": 255.0}, ...]

# Sort by total (descending)
sorted_sales = sorted(high_value, key=lambda x: x["total"], reverse=True)

# Calculate grand total
grand_total = sum(item["total"] for item in totals)

# One-liner version (combining comprehension and sum)
grand_total = sum(
    int(qty) * float(price)
    for name, qty, price in [line.split(",") for line in sales_data]
)
# 686.25

Example 2: Text Analysis

text = """
Python is amazing. Python is powerful.
Python is easy to learn. Python is versatile.
"""

# Clean and tokenize
words = [
    word.lower().strip(".,!?")
    for line in text.strip().split("\n")
    for word in line.split()
    if word.strip(".,!?")  # Remove empty strings
]
# ["python", "is", "amazing", "python", "is", "powerful", ...]

# Count word frequency
word_freq = {
    word: words.count(word)
    for word in set(words)
}
# {"python": 4, "is": 4, "amazing": 1, "powerful": 1, ...}

# Get top 5 most common words
top_words = sorted(
    word_freq.items(),
    key=lambda x: x[1],
    reverse=True
)[:5]
# [("python", 4), ("is", 4), ("to", 1), ...]

# Filter stop words
stop_words = {"is", "to", "the", "a", "an"}
content_words = [word for word in words if word not in stop_words]
# ["python", "amazing", "python", "powerful", ...]

# Get unique content words (sorted by length, then alphabetically)
unique_content = sorted(
    set(content_words),
    key=lambda w: (len(w), w)
)
# ["easy", "learn", "python", "amazing", "powerful", "versatile"]

Example 3: Coordinate Transformations

# Starting points
points = [(1, 2), (3, 4), (5, 6), (7, 8)]

# Translate all points (add offset)
offset_x, offset_y = 10, 20
translated = [(x + offset_x, y + offset_y) for x, y in points]
# [(11, 22), (13, 24), (15, 26), (17, 28)]

# Scale all points
scale = 2
scaled = [(x * scale, y * scale) for x, y in points]
# [(2, 4), (6, 8), (10, 12), (14, 16)]

# Calculate distances from origin
distances = [
    (x, y, (x**2 + y**2) ** 0.5)
    for x, y in points
]
# [(1, 2, 2.236...), (3, 4, 5.0), ...]

# Filter points in first quadrant (x > 0 and y > 0)
first_quadrant = [(x, y) for x, y in points if x > 0 and y > 0]
# [(1, 2), (3, 4), (5, 6), (7, 8)] (all of them)

# Find closest point to origin
closest = min(points, key=lambda p: (p[0]**2 + p[1]**2) ** 0.5)
# (1, 2)

# Calculate midpoints between consecutive points
midpoints = [
    ((x1 + x2) / 2, (y1 + y2) / 2)
    for (x1, y1), (x2, y2) in zip(points, points[1:])
]
# [(2.0, 3.0), (4.0, 5.0), (6.0, 7.0)]

Example 4: Student Grade Processing

# Student data
students = ["Alice", "Bob", "Charlie", "David", "Eve"]
test1 = [85, 78, 92, 88, 95]
test2 = [90, 82, 88, 85, 98]
test3 = [88, 80, 95, 90, 92]

# Combine all data
student_data = list(zip(students, test1, test2, test3))
# [("Alice", 85, 90, 88), ...]

# Calculate averages
averages = [
    {
        "name": name,
        "scores": [t1, t2, t3],
        "average": (t1 + t2 + t3) / 3,
        "grade": (
            "A" if (t1 + t2 + t3) / 3 >= 90
            else "B" if (t1 + t2 + t3) / 3 >= 80
            else "C" if (t1 + t2 + t3) / 3 >= 70
            else "F"
        )
    }
    for name, t1, t2, t3 in student_data
]

# Find students with all scores above 80
consistent_performers = [
    name for name, t1, t2, t3 in student_data
    if all(score > 80 for score in [t1, t2, t3])
]
# ["Alice", "Charlie", "David", "Eve"]

# Calculate improvement (test3 - test1)
improvements = {
    name: t3 - t1
    for name, t1, t2, t3 in student_data
}
# {"Alice": 3, "Bob": 2, "Charlie": 3, "David": 2, "Eve": -3}

# Most improved student
most_improved = max(improvements.items(), key=lambda x: x[1])
# ("Alice", 3) or ("Charlie", 3)

# Class statistics
all_scores = [score for _, *scores in student_data for score in scores]
class_stats = {
    "average": sum(all_scores) / len(all_scores),
    "highest": max(all_scores),
    "lowest": min(all_scores),
    "passing": len([s for s in all_scores if s >= 70])
}
# {"average": 88.4, "highest": 98, "lowest": 78, "passing": 15}

Performance & Best Practices

✓ Do This
  • Use list comprehensions over map/filter for readability
  • Keep comprehensions simple and readable
  • Use lambdas for simple, one-line operations
  • Use zip() for parallel iteration
  • Prefer built-in functions (int, len) with map
  • Chain operations when it stays readable
✗ Avoid This
  • Don't make comprehensions too complex
  • Don't use lambdas for multi-line logic
  • Don't nest comprehensions more than 2 levels
  • Don't assign lambdas to variables (use def)
  • Don't sacrifice readability for brevity
  • Don't use map/filter if list comp is clearer

When to Use Each Approach

# ✓ GOOD: Simple and readable
squares = [x ** 2 for x in range(10)]

# ✗ BAD: Too complex
result = [
    y ** 2
    for x in range(10) if x % 2 == 0
    for y in range(x) if y % 3 == 0
]  # Hard to understand!

# ✓ GOOD: Use regular function for complex logic
def process_item(item):
    """Complex processing logic"""
    if item > 100:
        return item * 0.9
    elif item > 50:
        return item * 0.95
    else:
        return item

results = [process_item(x) for x in data]

# ✗ BAD: Lambda for complex logic
results = [
    (lambda x: x * 0.9 if x > 100 else x * 0.95 if x > 50 else x)(x)
    for x in data
]  # Unreadable!

# ✓ GOOD: Named function instead of assigned lambda
def double(x):
    return x * 2

# ✗ BAD: Assigning lambda to variable
double = lambda x: x * 2  # Just use def!

# ✓ GOOD: Use existing function with map
strings = ["1", "2", "3"]
integers = list(map(int, strings))

# ✓ ALSO GOOD: List comprehension
integers = [int(s) for s in strings]

Key Takeaways

Comprehensions

  • List: [expr for item in iterable]
  • Dict: {k: v for item in iterable}
  • Set: {expr for item in iterable}
  • Add if for filtering
  • Use if-else for conditional expressions
  • Keep them simple and readable!

Lambda Functions

  • lambda args: expression
  • Perfect for sorting with key=
  • Use with map(), filter(), sorted()
  • Keep to one-line operations
  • For complex logic, use def
  • Don't assign to variables

Built-in Functions

  • map(func, iterable) - transform
  • filter(func, iterable) - select
  • zip(*iterables) - combine
  • Return iterators (use list() to materialize)
  • List comprehensions often more Pythonic
  • zip() is great for parallel iteration

Best Practices

  • Readability over cleverness
  • List comps >map/filter (usually)
  • Break complex logic into steps
  • Use meaningful variable names
  • Test with simple examples first
  • Embrace the Pythonic way!
What's Next?

You've mastered comprehensions and lambda functions - powerful tools for writing Pythonic code! In the next lessons, we'll explore:

  • Standard library essentials - Discover powerful built-in modules for dates, files, and more
  • Debugging techniques - Learn to find and fix bugs efficiently with print debugging and Python's debugger
  • Python Intermediate - When ready, dive deeper with iterators, generators, decorators, and advanced patterns