Lambda, map and filter
In Python, functions are values, just like numbers and strings. You can store them in variables, put them in lists and pass them to other functions. This lesson shows how, and introduces lambda, a quick way to write tiny functions on the spot.
Functions are values
A function name without () refers to the function itself, without calling it:
def shout(text):
return text.upper() + "!"
# no parentheses: not calling it
yell = shout
print(yell("hello"))
print(type(shout))yell = shout doesn't run anything. It gives the same function a second name, so yell("hello") and shout("hello") do exactly the same thing. Compare result = shout("hello"), which calls the function and stores its return value, "HELLO!".
So you can pass a function into another function:
def apply_twice(func, value):
return func(func(value))
def add_three(n):
return n + 3
print(apply_twice(add_three, 10))add_three is passed without parentheses, so apply_twice receives the function itself and calls it as func. func(func(10)) becomes add_three(add_three(10)), which is add_three(13), which is 16.
Lambda functions
A lambda is a small function written in a single expression, without def or a name:
lambda parameters: expression
These two do the same thing:
def square(n):
return n * n
square_lambda = lambda n: n * n
print(square(6))
print(square_lambda(6))Reading lambda n: n * n piece by piece:
| Part | Meaning |
|---|---|
lambda |
"here comes a small function" |
n |
its parameter (several are separated by commas; there are no parentheses) |
: |
separates the parameters from the result |
n * n |
the value it returns. There's no return keyword; the expression is the return value |
A lambda can take several parameters, and its expression is returned automatically:
add = lambda a, b: a + b
full_name = lambda first, last: f"{first} {last}".title()
print(add(2, 3))
print(full_name("ada", "lovelace"))Lambdas are limited to one expression: no loops, no assignments, no multiple statements. Their real use is as short throwaway functions passed to other functions. Storing one in a variable, as above, is just for demonstration. If a function deserves a name, write it with def, which also gives it a docstring and clearer error messages.
Sorting with a key
sorted(), min() and max() accept a key function. It's called on each item, and the results decide the order. This is where lambdas are used most:
words = ["banana", "Apple", "cherry", "date"] print(sorted(words)) print(sorted(words, key=str.lower)) print(sorted(words, key=len)) print(sorted(words, key=lambda w: w[-1]))
key= |
Python compares | Result |
|---|---|---|
| (none) | the words themselves; capitals come first | Apple, banana, cherry, date |
str.lower |
the lowercase versions | Apple, banana, cherry, date |
len |
the lengths: 6, 5, 6, 4 | date, Apple, banana, cherry |
lambda w: w[-1] |
the last letters | banana, Apple, date, cherry |
The key is only used for comparing. The result still contains the original items, unchanged; sorted(words, key=len) gives words, not numbers. When two items have the same key (like banana and cherry, both length 6), they keep their original order. Pass the function itself, without (): key=len, not key=len().
It's especially handy for lists of tuples or dictionaries:
students = [("Mia", 88), ("Leo", 95), ("Ava", 72)]
by_score = sorted(students, key=lambda s: s[1], reverse=True)
print(by_score)
best = max(students, key=lambda s: s[1])
print("Top student:", best[0])Each student is a tuple (name, score), so s[1] is the score. key=lambda s: s[1] means "compare the students by their score". max() with a key gives back the whole tuple, ("Leo", 95), which is why the last line uses best[0] for the name.
products = [
{"name": "Laptop", "price": 999},
{"name": "Mouse", "price": 25},
{"name": "Monitor", "price": 249},
]
cheapest_first = sorted(products, key=lambda p: p["price"])
for p in cheapest_first:
print(p["name"], p["price"])map(): apply a function to every item
map(function, iterable) calls the function on each item. It returns a lazy map object, so wrap it in list() to see the results:
numbers = [1, 2, 3, 4] doubled = map(lambda n: n * 2, numbers) print(doubled) print(list(doubled)) print(list(map(str.upper, ["a", "b", "c"]))) # convert strings to ints print(list(map(int, ["10", "20", "30"])))
"Lazy" means map() doesn't do any work until you ask for the results, which is why printing doubled shows only <map object at ...>. A map object can be used once: after list(doubled) has gone through it, a second list(doubled) gives []. If you need the results more than once, store the list.
A common trick reads several numbers from one line of input:
line = "3 8 15 4" numbers = list(map(int, line.split())) print(numbers, sum(numbers))
filter(): keep the items that pass a test
filter(function, iterable) keeps the items for which the function returns something truthy:
numbers = [5, 12, 7, 20, 3, 18] big = filter(lambda n: n > 10, numbers) print(list(big)) # None: keep only truthy items words = ["", "hello", "", "world"] print(list(filter(None, words)))
The function is called on each item: 5 > 10 is False, so 5 is dropped; 12 > 10 is True, so 12 is kept. Like map(), filter() is lazy, so it's wrapped in list(). Passing None instead of a function keeps the items that are truthy by themselves, which here removes the empty strings.
| Function | Gives back | Length of the result |
|---|---|---|
map(f, items) |
f(item) for every item |
the same as items |
filter(f, items) |
the items where f(item) is truthy, unchanged |
the same or shorter |
Comprehensions are often clearer
Anything map and filter do, a list comprehension can do too, and many Python programmers find the comprehension easier to read:
numbers = [5, 12, 7, 20, 3, 18] print(list(map(lambda n: n * 2, numbers))) print([n * 2 for n in numbers]) print(list(filter(lambda n: n > 10, numbers))) print([n for n in numbers if n > 10])
A good rule of thumb:
- Use
mapwhen you already have a named function:map(int, parts). - Use a comprehension when you'd need a lambda.
- Use a lambda mostly for
key=arguments.
Going deeper: any() and all() optional
Two more built-ins answer yes-or-no questions about a whole collection:
any(items)isTrueif at least one item is truthy.all(items)isTrueif every item is truthy.
They pair nicely with a comprehension that turns each item into True or False:
scores = [72, 85, 90, 66] # did anyone get 90 or more? print(any(s >= 90 for s in scores)) # did everyone pass? print(all(s >= 50 for s in scores)) print(all(s >= 70 for s in scores))
Notice there are no square brackets inside any() and all(). That's a generator expression, which you'll meet in Iterators and Generators. Writing any([s >= 90 for s in scores]) with brackets works too.
Both stop as soon as they know the answer, so any() stops at the first True. On an empty collection, any([]) is False and all([]) is True, because no item breaks the rule.
Exercises
Exercise 1: Sort by length
Sort the names from shortest to longest using sorted() with a key. It should print ['Al', 'Mia', 'Grace', 'Alexander'].
names = ["Grace", "Al", "Alexander", "Mia"]
sorted() takes a key function that decides the order.
The built-in len already gives each name's length: sorted(names, key=len).
Exercise 2: Most expensive item
Use max() with a lambda to find the most expensive item, and print its name: Laptop.
items = [("Pen", 1.5), ("Laptop", 899.0), ("Book", 12.99)]
Each item is a tuple (name, price), so the price is item[1].
Use max(items, key=lambda item: item[1]), then print the first part of the result.
Exercise 3: map and filter
Use filter to keep the odd numbers, then map to square them, and print the list: [1, 9, 25, 49, 81].
numbers = list(range(1, 11))
filter(lambda n: n % 2 == 1, numbers) keeps the odd numbers.
Pass that into map(lambda n: n ** 2, ...) and wrap the result in list().
Exercise 4: Rewrite as a comprehension
Rewrite the map/filter line as a single list comprehension that gives the same result.
words = ["python", "is", "fun", "and", "powerful"] result = list(map(str.upper, filter(lambda w: len(w) > 3, words))) print(result)
map(str.upper, ...) becomes w.upper() at the front of the comprehension.
The filter condition becomes if len(w) > 3 at the end.
Functions that take functions at a glance
A quick reference for later. The examples use words = ["kiwi", "fig", "banana"] and nums = [3, -5, 2].
| Tool | What it does | Example | Result |
|---|---|---|---|
lambda args: expr |
A small one-line function | (lambda x: x * 2)(5) |
10 |
sorted(items, key=f) |
Sorts by f(item) instead of the item itself |
sorted(words, key=len) |
['fig', 'kiwi', 'banana'] |
sorted(items, key=f, reverse=True) |
The same, largest first | sorted(words, key=len, reverse=True) |
['banana', 'kiwi', 'fig'] |
items.sort(key=f) |
Like sorted(), but sorts the list in place |
words.sort(key=len) |
words becomes ['fig', 'kiwi', 'banana'] |
min(items, key=f) |
The item with the smallest f(item) |
min(nums, key=abs) |
2 |
max(items, key=f) |
The item with the largest f(item) |
max(words, key=len) |
'banana' |
map(f, items) |
Applies f to every item |
list(map(abs, nums)) |
[3, 5, 2] |
filter(f, items) |
Keeps the items where f(item) is truthy |
list(filter(lambda n: n > 0, nums)) |
[3, 2] |
filter(None, items) |
Keeps only the truthy items | list(filter(None, [0, 1, "", "a"])) |
[1, 'a'] |
any(...) |
Is at least one item truthy? | any(n < 0 for n in nums) |
True |
all(...) |
Is every item truthy? | all(n < 0 for n in nums) |
False |
Summary
- Functions are values: you can store them and pass them to other functions.
lambda params: expressionmakes a small one-expression function.sorted,minandmaxtake akey=function that decides the order.map(func, items)transforms every item, andfilter(func, items)keeps the items that pass.- Wrap
mapandfilterinlist()to see the results. - When you'd need a lambda, a list comprehension is often clearer.
Know something worth teaching? Write your own tutorial with runnable examples and share it.