Lambda Basics
A lambda is a one-line anonymous function. Syntax: lambda parameters: expression. The expression is automatically returned.
# Lambda vs equivalent def
double_lambda = lambda x: x * 2
def double_def(x):
return x * 2
print(double_lambda(5)) # 10
print(double_def(5)) # 10
# Multiple parameters
add = lambda a, b: a + b
print(add(3, 4)) # 7
# Default parameters
greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Hi")) # Hi, Bob!
# No parameters
pi = lambda: 3.14159
print(pi()) # 3.14159
# Lambda is an expression - can be used inline
result = (lambda x, y: x + y)(10, 20)
print(result) # 30
# Lambda with conditional expression
classify = lambda n: "positive" if n > 0 else "negative" if n < 0 else "zero"
print(classify(5)) # positive
print(classify(-3)) # negative
print(classify(0)) # zero
# Don't do this (PEP 8 says don't assign lambda to a variable)
# f = lambda x: x + 1 # use def instead:
def f(x):
return x + 1
map() and filter()
map() applies a function to every item. filter() keeps items where the function returns truthy. Both return lazy iterators.
# --- map(function, iterable) ---
numbers = [1, 2, 3, 4, 5]
# Lambda with map
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Built-in function with map (faster than lambda)
strings = ["1", "2", "3", "4"]
ints = list(map(int, strings))
print(ints) # [1, 2, 3, 4]
words = ["hello", "world", "python"]
upper = list(map(str.upper, words))
print(upper) # ['HELLO', 'WORLD', 'PYTHON']
# map with multiple iterables
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
print(sums) # [11, 22, 33]
# Equivalent list comprehension (usually preferred)
squared_comp = [x**2 for x in numbers]
print(squared_comp) # [1, 4, 9, 16, 25]
# --- filter(function, iterable) ---
numbers = range(-5, 6)
positives = list(filter(lambda x: x > 0, numbers))
print(positives) # [1, 2, 3, 4, 5]
# filter(None, iterable) removes falsy values
mixed = [0, 1, "", "hello", None, [], [1, 2], False, True]
truthy = list(filter(None, mixed))
print(truthy) # [1, 'hello', [1, 2], True]
# Equivalent comprehension
positives_comp = [x for x in range(-5, 6) if x > 0]
print(positives_comp)
# Chaining map and filter
data = [" alice ", "", " BOB ", " ", "charlie"]
cleaned = list(filter(None, map(str.strip, data)))
print(cleaned) # ['alice', 'BOB', 'charlie']
# Same with comprehension (arguably clearer)
cleaned2 = [s.strip() for s in data if s.strip()]
print(cleaned2)
sorted() with key=
The key= argument is the most common use of lambda in Python. It specifies a function to extract the sort key from each item.
import operator
# Sort strings by length
words = ["banana", "apple", "cherry", "fig", "date"]
by_length = sorted(words, key=lambda w: len(w))
print(by_length) # ['fig', 'date', 'apple', 'banana', 'cherry']
# Sort strings case-insensitively
words2 = ["Banana", "apple", "Cherry", "DATE"]
by_lower = sorted(words2, key=lambda w: w.lower())
print(by_lower) # ['apple', 'Banana', 'Cherry', 'DATE']
# Better: key=str.casefold
# Sort by last name
names = ["Alice Smith", "Charlie Brown", "Bob Jones"]
by_last = sorted(names, key=lambda n: n.split()[-1])
print(by_last) # ['Charlie Brown', 'Bob Jones', 'Alice Smith']
# Sort list of dicts
people = [
{"name": "Charlie", "age": 35, "score": 88},
{"name": "Alice", "age": 25, "score": 95},
{"name": "Bob", "age": 30, "score": 72},
]
by_age = sorted(people, key=lambda p: p["age"])
by_score = sorted(people, key=lambda p: p["score"], reverse=True)
# operator.itemgetter is faster and more readable for simple lookups
by_age2 = sorted(people, key=operator.itemgetter("age"))
print([p["name"] for p in by_age2]) # ['Alice', 'Bob', 'Charlie']
# Sort by multiple fields: primary by age, secondary by score
by_multi = sorted(people, key=lambda p: (p["age"], -p["score"]))
# Sort objects by attribute
from operator import attrgetter
class Student:
def __init__(self, name, gpa):
self.name = name
self.gpa = gpa
def __repr__(self):
return f"Student({self.name}, {self.gpa})"
students = [Student("Alice", 3.9), Student("Bob", 3.5), Student("Charlie", 3.7)]
by_gpa = sorted(students, key=attrgetter("gpa"), reverse=True)
print(by_gpa) # [Student(Alice, 3.9), Student(Charlie, 3.7), Student(Bob, 3.5)]
functools.reduce()
reduce() applies a function cumulatively to a sequence, reducing it to a single value.
from functools import reduce
import operator
numbers = [1, 2, 3, 4, 5]
# Sum using reduce (prefer sum() in practice)
total = reduce(lambda acc, x: acc + x, numbers)
print(total) # 15
# Product (no built-in)
product = reduce(lambda acc, x: acc * x, numbers)
print(product) # 120
# Same using operator module
product2 = reduce(operator.mul, numbers)
print(product2) # 120
# With initial value
total_from_100 = reduce(lambda acc, x: acc + x, numbers, 100)
print(total_from_100) # 115
# Maximum without max() (just for illustration)
max_val = reduce(lambda a, b: a if a > b else b, numbers)
print(max_val) # 5
# Deep merge dicts
dicts = [{"a": 1}, {"b": 2}, {"c": 3}]
merged = reduce(lambda a, b: {**a, **b}, dicts)
print(merged) # {'a': 1, 'b': 2, 'c': 3}
# In practice, prefer built-ins over reduce:
# sum(), max(), min(), any(), all(), " ".join()
print(sum(numbers)) # 15 (cleaner than reduce)
print(max(numbers)) # 5
print(" ".join(["a","b"])) # a b
Lambda Limitations
# Lambdas can only contain ONE expression
# These are NOT valid in lambda:
# - assignments
# - if/elif/else statements (only ternary expression)
# - loops
# - try/except
# - multiple statements
# WRONG: assignment in lambda
# f = lambda x: y = x + 1 # SyntaxError
# WRONG: for loop in lambda
# f = lambda xs: for x in xs: print(x) # SyntaxError
# Lambdas cannot have docstrings
def documented(x):
"""This function squares x."""
return x ** 2
# Lambda has no docstring:
sq = lambda x: x ** 2
print(sq.__doc__) # None
# Lambda has no name (shows as )
print(sq.__name__) # - unhelpful for debugging
print(documented.__name__) # documented
# Rule: when a lambda becomes complex, convert to def
# BAD: unreadable lambda
process = lambda data: [x**2 for x in data if x > 0 and x % 2 == 0]
# GOOD: readable named function
def process_evens(data):
"""Return squares of positive even numbers."""
return [x**2 for x in data if x > 0 and x % 2 == 0]
# Summary: use lambda only as a short key/callback function
# For everything else, use def
# Good lambda uses (concise key functions):
data = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
sorted(data, key=lambda d: d["age"]) # clear and concise
# Equivalent with operator (even better for simple attribute/item access):
from operator import itemgetter
sorted(data, key=itemgetter("age"))
operator.itemgetter("key") is faster than lambda d: d["key"] and more explicit. Similarly, operator.attrgetter("attr") beats lambda obj: obj.attr. These are micro-optimizations, but they also signal intent more clearly.