Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 4 – Functions, scope, closures and decorators

Lambdas and higher-order functions

Write small lambdas for sort keys and callbacks, use map, filter and sorted with key, and see when itemgetter, attrgetter or a comprehension reads better.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 25 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Pass functions as arguments by name, without calling them
  • Write small lambdas for sort keys and callbacks
  • Sort on several keys with a key function and with two stable sorts
  • Choose between map(), filter(), a comprehension and operator.itemgetter()
  • Decide when functools.reduce() helps and when a built-in such as sum or max is clearer

Before you start

What you’ll learn · Before you start

What you will learn

  • Pass functions as arguments by name, without calling them
  • Write small lambdas for sort keys and callbacks
  • Sort on several keys with a key function and with two stable sorts
  • Choose between map(), filter(), a comprehension and operator.itemgetter()
  • Decide when functools.reduce() helps and when a built-in such as sum or max is clearer

Before you start

On this page

A contest leaderboard of six rows must be ordered by three things at once: most problems solved, then the lowest penalty, then the name. sorted() does it in one line, as long as you hand it a function that turns each row into something to compare. A function that takes another function, as sorted() takes its key, or returns one, is a higher-order function: A function that takes another function as an argument, returns one, or both. sorted() is one: it calls the key function you pass once for every item., and a lambda: An expression that makes a small function without a name: lambda r: r[1] takes r and returns r[1]. Its body is one expression, never a statement, and its name is always <lambda>. is the shortest way to write the small function you hand over: lambda r: r["solved"] takes r and returns r["solved"], written in place, without a def.

Below you will pass functions without calling them, step through a sort that calls its key once per row, sort in two directions with two stable sorts, watch a filter() result run out, swap lambdas for itemgetter(), follow a callback, and see when functools.reduce() helps and when a built-in says it better.

A function is a value you can pass without calling it

In Python a function is an object, just like a number or a list. Its name refers to the object, and you can pass the object to another function without calling it, by leaving out the parentheses. sorted(words, key=str.lower) hands over the method str.lower itself; sorted() then calls it once for every word and compares the results:

Passing functions to sorted() and to your own function Python · pass_a_function.py
# A function is a value: it can be passed to another function without calling it.
words = ["kiwi", "Banana", "fig", "apple", "Cherry"]

print(sorted(words))                 # capital letters sort before small ones
print(sorted(words, key=str.lower))  # compare the lower-case forms instead
print(sorted(words, key=len))        # shortest first; equal lengths keep their order


def shout(text):
    return text.upper() + "!"


def apply_twice(func, value):
    """A higher-order function: it takes another function as an argument."""
    return func(func(value))


print(apply_twice(shout, "hey"))
print(type(shout).__name__, shout.__name__)

Output

['Banana', 'Cherry', 'apple', 'fig', 'kiwi']
['apple', 'Banana', 'Cherry', 'fig', 'kiwi']
['fig', 'kiwi', 'apple', 'Banana', 'Cherry']
HEY!!
function shout

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pass_a_function.py

  • Without a key, capital letters sort before small ones, because the comparison uses each character’s code point and "B" comes before "a". With key=str.lower, the words are compared in lower case but returned unchanged.
  • key=len sorts by length. "Banana" and "Cherry" both have six letters and stay in their original order, because Python’s sort keeps items with equal keys in the order they came in.
  • apply_twice() is a higher-order function you wrote yourself. It received shout, a plain function, and called it twice; type(shout) shows that a function’s type is simply function.

Step through the call apply_twice(shout, "hey") and watch which names refer to which object. The parentheses inside apply_twice() are what make the function run:

Interactive · step by step Passing shout to apply_twice() without calling it Step through apply_twice(shout, "hey"). Names are on the left, the objects they refer to on the right.
Passing shout to apply_twice() without calling it, step 5 of 5. Showing the name shout, a function object, the parameter func, the value ('HEY!!'). In focus: the name shout, the parameter func.refers tothe same objectcalled onthe name shouta global namea function objectshout(text)the parameter funcof apply_twice()the value'HEY!!'

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. def shout runs. Running def shout(text) makes a function object and binds the name shout to it, the way an assignment binds a name to a number. The body has not run yet.
  2. Passed, not called. apply_twice(shout, "hey") writes shout without parentheses, so it passes the function object itself. The parameter func now refers to that same object.
  3. The first call. Inside apply_twice(), func(value) calls the object with 'hey'. The parentheses are what make it run, and shout returns 'HEY!'.
  4. The second call. func(func(value)) calls the object again on that result, which gives 'HEY!!'. apply_twice() returns it, and the program prints HEY!!.
  5. One object, two names. type(shout) is function and shout.__name__ is still 'shout'. Passing a function hands over the object; it is never copied or renamed, and sorted(words, key=str.lower) works the same way.

shout and func are two names for one function object. Passing a function hands the object over; nothing is called, copied or renamed until the receiving code writes the parentheses.

Calling the function instead of passing it

sorted(words, key=str.lower()) calls str.lower at once, with no string to work on, and fails with TypeError: unbound method str.lower() needs an argument before sorted() starts. The key is the function, key=str.lower, never its result.

lambda writes a one-expression function in place

lambda parameters: expression creates a function whose body is that single expression; calling it returns the value of the expression. lambda x: x * x does the same work as a def that returns x * x, and its parameters follow the same rules, so a default value: The value after = in a def line, as in exponent=2, which a parameter takes when a call leaves it out. Python evaluates it once, when the def runs. More in “Parameters: defaults, keyword-only, positional-only” and *args work too. What cannot go inside is a statement: no assignment with =, no return, no for loop, no try. A conditional expression is allowed, because it is an expression: lambda n: "even" if n % 2 == 0 else "odd". A lambda also cannot carry type annotations. The program squares 7 both ways, then shows the name each kind of function carries:

A lambda next to a def Python · lambda_vs_def.py
import traceback


def square(x):
    return x * x


print(square(7), (lambda x: x * x)(7))   # the same work, with and without a name

parity = lambda n: "even" if n % 2 == 0 else "odd"   # works, but PEP 8 asks for def
print(parity(10), parity(7))
print(square.__name__, parity.__name__)

# Tracebacks name the function that failed: here it is only "<lambda>".
try:
    (lambda x: 10 / x)(0)
except ZeroDivisionError as err:
    frames = traceback.extract_tb(err.__traceback__)
    print("failed in:", [frame.name for frame in frames])

Output

49 49
even odd
square <lambda>
failed in: ['<module>', '<lambda>']

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lambda_vs_def.py

The difference that matters in practice is the name. A function made with def square is called square in tracebacks, profiles and its repr; every lambda is called <lambda>, so when one fails, the traceback cannot tell you which of your lambdas it was, as the last line of the output shows. That is why PEP 8: Python's style guide: the naming, layout and comment conventions most Python code follows, such as snake_case names for functions, four-space indents and lines of at most 79 characters. More in “Docstrings, comments and PEP 8 style”, Python’s style guide, asks for a def instead of an assignment such as parity = lambda n: …. The table sums the two up:

lambda or def
Criterion lambdadef
Body One expression, whose value is returnedAny statements, with return for the result
Name Always <lambda>, in tracebacks and repr()The name after def, such as square
Docstring and annotations NeitherBoth
Parameters The rules of def: defaults, *args, **kwargsThe same rules
When to choose A small function passed straight into a call, such as a sort keyAnything you name, reuse, document or test

Key idea

A lambda is an ordinary function with one expression for a body and no name of its own: use it inline, and give a function that deserves a name a def.

Checkpoint

Check: Passing a function as a value · Writing a lambda

  1. Question 1 of 2 What does this program print?

    Read the code, then choose one answer.

    words = ["kiwi", "Banana", "apple"]
    print(sorted(words, key=str.lower()))
    A hint

    Arguments are worked out before the call starts. What is str.lower() on its own?

    Show the answer: Question 1 of 2

    Answer: TypeError: unbound method str.lower() needs an argument

    Python works out every argument before it calls a function, so key=str.lower() calls str.lower right away. It needs a string and gets none, and Python raises TypeError. Write key=str.lower, the method itself: sorted() then calls it once for each word.

  2. Question 2 of 2 Which of these are valid lambdas?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • lambda n: "even" if n % 2 == 0 else "odd"
    • lambda x, y=10: x + y

    A lambda's body is a single expression, and its value is what a call returns. Conditional expressions and default values are fine; statements such as return, assignments with =, loops and try are not, and they are a SyntaxError.

sorted() calls the key once per item, then compares the keys

sorted(iterable, key=None, reverse=False) returns a new sorted list and leaves its input alone, and key and reverse can only be passed by keyword; list.sort() takes the same two arguments, sorts the list in place and returns None. The key function: A function that sorted(), list.sort(), min() and max() call once for every item; they then compare its results instead of the items. key=len compares the lengths. is called exactly once for each item, and the sort then compares those keys instead of the items. A key can be any value that compares, and tuples compare item by item, which is what makes sorting on several criteria a one-liner:

A leaderboard sorted on three keys Python · leaderboard.py
# A contest leaderboard: most problems solved first, then the lower penalty, then the name.
results = [
    {"name": "Meera", "solved": 4, "penalty": 95},
    {"name": "Arjun", "solved": 5, "penalty": 130},
    {"name": "Kabir", "solved": 4, "penalty": 95},
    {"name": "Divya", "solved": 5, "penalty": 102},
    {"name": "Sana", "solved": 3, "penalty": 40},
    {"name": "Rohan", "solved": 4, "penalty": 88},
]

board = sorted(results, key=lambda r: (-r["solved"], r["penalty"], r["name"]))
for place, r in enumerate(board, start=1):
    print(place, r["name"], r["solved"], r["penalty"])

# How often does sorted() call the key? A key function that keeps a log shows it.
log = []


def penalty_key(row):
    log.append(row["name"])
    return row["penalty"]


sorted(results, key=penalty_key)
print("key calls:", len(log), "for", len(results), "rows")

# Text cannot be negated, so names from Z to A need two stable sorts:
# first by the minor key, then by the major key.
by_name = sorted(results, key=lambda r: r["name"], reverse=True)
by_solved = sorted(by_name, key=lambda r: r["solved"], reverse=True)
print([r["name"] for r in by_solved])

Output

1 Divya 5 102
2 Arjun 5 130
3 Rohan 4 88
4 Kabir 4 95
5 Meera 4 95
6 Sana 3 40
key calls: 6 for 6 rows
['Divya', 'Arjun', 'Rohan', 'Meera', 'Kabir', 'Sana']

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 leaderboard.py

Each leaderboard row becomes a key tuple; sorted() orders the tuples, so Arjun comes first, then Rohan, then Meera.1. The rows, in input order Meera · 4 solved · 95 minArjun · 5 solved · 130 minRohan · 4 solved · 88 min2. One key per row (-4, 95, 'Meera')(-5, 130, 'Arjun')(-4, 88, 'Rohan')3. Sorted by key, smallest first 1st · Arjun · (-5, 130, …)2nd · Rohan · (-4, 88, …)3rd · Meera · (-4, 95, …)key(row) = (-solved, penalty, name)compare the tuples

sorted() compares the keys, not the rows

Text description of the diagram

The diagram has three boxes, one above the other.

  • The first box holds three rows of the leaderboard in the order they were given: Meera with 4 problems solved and a penalty of 95 minutes, Arjun with 5 solved and 130 minutes, and Rohan with 4 solved and 88 minutes.
  • An arrow labelled "key(row) = (-solved, penalty, name)" leads to the second box. sorted() calls the key function once for each row, and the keys are (-4, 95, 'Meera'), (-5, 130, 'Arjun') and (-4, 88, 'Rohan').
  • An arrow labelled "compare the tuples" leads to the third box, the result. Tuples are compared item by item, so (-5, …) is smallest and Arjun is first. Rohan and Meera both start with -4, so their penalties decide: 88 is less than 95, and Rohan comes second, Meera third.

Negating the number solved turns "most solved first" into an ordinary smallest-first sort.

The key (-r["solved"], r["penalty"], r["name"]) puts the most problems solved first by negating them, because the sort is always smallest first; then the lower penalty; then the name, so Kabir comes before Meera, who solved as many problems with the same penalty. The diagram shows three of the rows. Step through the same sort on all six, one key call per row and then the comparisons that decide the places:

Interactive · step by step One key call for each of the six rows Step through the sort of leaderboard.py. Each row is on the left, the key the lambda returns for it on the right.
One key call for each of the six rows, step 8 of 8. Showing Meera · 4 · 95, Arjun · 5 · 130, Kabir · 4 · 95, Divya · 5 · 102, Sana · 3 · 40, Rohan · 4 · 88, (-4, 95, 'Meera') (5th place), (-5, 130, 'Arjun') (2nd place), (-4, 95, 'Kabir') (4th place), (-5, 102, 'Divya') (1st place), (-3, 40, 'Sana') (6th place), (-4, 88, 'Rohan') (3rd place). In focus: (-3, 40, 'Sana').keykeykeykeykeykeyMeera · 4 · 95Arjun · 5 · 130Kabir · 4 · 95Divya · 5 · 102Sana · 3 · 40Rohan · 4 · 88(-4, 95, 'Meera')5th place(-5, 130, 'Arjun')2nd place(-4, 95, 'Kabir')4th place(-5, 102, 'Divya')1st place(-3, 40, 'Sana')6th place(-4, 88, 'Rohan')3rd place

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Six rows. Each row is a name, the number of problems solved and the penalty in minutes. Before it compares anything, sorted() calls the key function once on every row, in the order the rows came.
  2. Key call 1. The lambda gets Meera's row and returns the tuple (-4, 95, 'Meera'): the number solved, negated, then the penalty, then the name.
  3. Key call 2. Arjun solved 5, so his key starts with -5: negating the number solved makes more solved a smaller key, and the sort puts smaller keys first.
  4. Key calls 3 and 4. Kabir's key is (-4, 95, 'Kabir'), the same as Meera's apart from the name. Divya's is (-5, 102, 'Divya').
  5. Key calls 5 and 6. Sana's key is (-3, 40, 'Sana') and Rohan's (-4, 88, 'Rohan'). That makes six calls for six rows, and the lambda will not be called again.
  6. Compare the first items. Now the sort compares keys only, item by item. -5 is the smallest first item, so Divya and Arjun come first, and Divya's penalty of 102 is below Arjun's 130.
  7. Ties on the number solved. Rohan, Kabir and Meera all start with -4, so the penalties decide: 88 comes before 95. Kabir and Meera tie on 95 as well, and the names decide: 'Kabir' comes before 'Meera'.
  8. The order. Sana's -3 is the largest first item, so she is last. The rows come out in the order of their keys, Divya, Arjun, Rohan, Kabir, Meera and Sana, which is the leaderboard the program printed.

Six rows, six key calls, which is what the logging key function of the program counted. A key function that takes a while, such as one that parses a timestamp, therefore costs one parse per row, however many comparisons the sort makes. Two things to check in your own keys: every key must compare with every other, so a missing value such as None among numbers raises TypeError, and a key that builds big objects for millions of rows uses memory for all of them at once.

Key idea

sorted() calls the key once per item and then compares only the keys, so a tuple key sorts on several criteria at once, and negating a number turns “highest first” into an ordinary smallest-first sort.

Two stable sorts order keys that go different ways

Negation only works for numbers. To sort names from Z to A while the number solved stays the main order, sort twice: first by the minor key, the name, then by the major key, the number solved. This works because Python’s sort is a stable sort: A sort that keeps items with equal keys in the order they came in. Python's sorted() and list.sort() are stable, so a second sort keeps the order of the first among ties.: items with equal keys keep the order they came in, and reverse=True keeps that promise too. Step through the two sorts on the six rows, as name and number pairs, and watch the three rows that solved 4:

Interactive · line by line Two stable sorts, names Z to A within each number solved Step through the two sorts and compare the three lists. Watch the order of the rows that solved 4.

Program · two_stable_sorts.py

rows = [("Meera", 4), ("Arjun", 5), ("Kabir", 4),
        ("Divya", 5), ("Sana", 3), ("Rohan", 4)]
by_name = sorted(rows, key=lambda r: r[0], reverse=True)
board = sorted(by_name, key=lambda r: r[1], reverse=True)
print(board)

Variables

After line 5: rows is [('Meera', 4), ('Arjun', 5), ('Kabir', 4), ('Divya', 5), ('Sana', 3), ('Rohan', 4)]; by_name is [('Sana', 3), ('Rohan', 4), ('Meera', 4), ('Kabir', 4), ('Divya', 5), ('Arjun', 5)]; board is [('Divya', 5), ('Arjun', 5), ('Rohan', 4), ('Meera', 4), ('Kabir', 4), ('Sana', 3)].rows('Meera', 4)0('Arjun', 5)1('Kabir', 4)2('Divya', 5)3('Sana', 3)4('Rohan', 4)5by_name('Sana', 3)0('Rohan', 4)1('Meera', 4)2('Kabir', 4)3('Divya', 5)4('Arjun', 5)5board('Divya', 5)0('Arjun', 5)1('Rohan', 4)2('Meera', 4)3('Kabir', 4)4('Sana', 3)5
  • changed in this step

Printed so far

[('Divya', 5), ('Arjun', 5), ('Rohan', 4), ('Meera', 4), ('Kabir', 4), ('Sana', 3)]

Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.

The drawing shows the variables when the program ends, and the program's 5 steps are listed below.

Line run 5
Next line end
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. rows = [("Meera", 4), ("Arjun", 5), ("Kabir", 4), ("Divya", 5), ("Sana", 3), ("Rohan", 4)] makes rows a list of 6 items.
  3. Line 3. by_name = sorted(rows, key=lambda r: r[0], reverse=True) sets by_name to [('Sana', 3), ('Rohan', 4), ('Meera', 4), ('Kabir', 4), ('Divya', 5), ('Arjun', 5)]. The first sort orders by the minor key, the name, from Z to A. reverse=True still keeps rows with equal keys in their original order.
  4. Line 4. board = sorted(by_name, key=lambda r: r[1], reverse=True) sets board to [('Divya', 5), ('Arjun', 5), ('Rohan', 4), ('Meera', 4), ('Kabir', 4), ('Sana', 3)]. Key point The second sort orders by the major key, the number solved. Rohan, Meera and Kabir all solved 4, so they keep the Z-to-A order the first sort gave them.
  5. Line 5. print(board) prints [('Divya', 5), ('Arjun', 5), ('Rohan', 4), ('Meera', 4), ('Kabir', 4), ('Sana', 3)]. That was the last line, so the program ends. Most solved first, and names from Z to A among equal numbers solved. A negated name is impossible, so two stable sorts do what one key cannot.

The second sort moved Divya and Arjun to the front, and it left Rohan, Meera and Kabir in the Z-to-A order the first sort gave them. The names read Divya, Arjun, Rohan, Meera, Kabir, Sana, the last line leaderboard.py printed. Press Edit, remove reverse=True from the first sort and run it: Kabir, Meera and Rohan now come out from A to Z.

Sorting by the main key first

Sorting by the number solved first and by the name second orders the rows by name, and the number solved only breaks ties between equal names, because the last sort decides the main order. Sort by the minor key first and the major key last.

Checkpoint

Check: Sorting with a key function · Two stable sorts

  1. Question 1 of 2 What does this program print?

    Read the code, then choose one answer.

    marks = [("Asha", 82), ("Ravi", 91), ("Meena", 77)]
    print(sorted(marks, key=lambda m: -m[1])[0])
    Show the answer: Question 1 of 2

    Answer: ('Ravi', 91)

    The key is called once for each tuple and gives -82, -91 and -77. Sorting those smallest first puts -91 first, so the tuple with 91 marks is at position 0: negating a number turns "highest first" into an ordinary sort.

  2. Question 2 of 2 Rows are (name, city) tuples. You want them by city from Z to A, and by name from A to Z within each city. Which code does it?

    Choose one answer.

    A hint

    Which sort decides the main order, the first or the last?

    Show the answer: Question 2 of 2

    Answer: by_name = sorted(rows, key=lambda r: r[0]), then sorted(by_name, key=lambda r: r[1], reverse=True)

    Sort by the minor key first, then by the major key. Python's sorts are stable, so the second sort keeps the order of the first among rows with the same city, and reverse=True does not change that. Negation would do it in one sort for numbers, never for text.

map() and filter() hand out their items only once

map(function, iterable) applies the function to every item of an iterable: A value a for loop can go through one item at a time, such as a string, a list, a tuple, a dict, a set or a range. A dict gives its keys. More in “for loops with range, enumerate and zip”, and filter(function, iterable) keeps the items for which the function returns a true value. Both return an An object that hands out the items of a collection one at a time, when asked for the next one. Once it has handed out the last item it is used up and gives nothing more.: they produce their items one at a time, when something asks for them, and only once. The program converts five temperatures with map(), keeps the hot ones with filter(), then does both again with comprehensions:

map() and filter() next to comprehensions Python · map_filter_comprehension.py
celsius = [31.5, 24.0, 38.2, 29.9, 35.0]

# map and filter take a function and an iterable, and return lazy iterators.
fahrenheit = map(lambda c: round(c * 9 / 5 + 32, 1), celsius)
print(fahrenheit.__class__.__name__)
print(list(fahrenheit))
print(list(fahrenheit))   # the iterator is used up: a second pass gives nothing

hot = filter(lambda c: c >= 30, celsius)
print(list(hot))

# The same results as comprehensions, which give a list you can reuse.
print([round(c * 9 / 5 + 32, 1) for c in celsius])
print([c for c in celsius if c >= 30])

# map shines when the function already exists: no lambda needed.
print(list(map(int, "12 7 30".split())))
print(list(filter(None, ["a", "", "b", None, 0, "c"])))   # None keeps truthy items

Output

map
[88.7, 75.2, 100.8, 85.8, 95.0]
[]
[31.5, 38.2, 35.0]
[88.7, 75.2, 100.8, 85.8, 95.0]
[31.5, 38.2, 35.0]
[12, 7, 30]
['a', 'b', 'c']

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 map_filter_comprehension.py

The second list(fahrenheit) is empty, because the first one used the iterator up. Step through the same thing with filter() and watch first and second:

Interactive · line by line A filter object read twice Step through the program and watch first and second. hot holds no list, only the filter object that hands out items.

Program · used_up.py

celsius = [31.5, 24.0, 38.2, 29.9, 35.0]
hot = filter(lambda c: c >= 30, celsius)
first = list(hot)
second = list(hot)
print(first, second)

Variables

After line 5: celsius is [31.5, 24.0, 38.2, 29.9, 35.0]; hot is <filter object>; first is [31.5, 38.2, 35.0]; second is [].celsius31.5024.0138.2229.9335.04hot<filter object>first31.5038.2135.02second
  • changed in this step
  • read next

Printed so far

[31.5, 38.2, 35.0] []

Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.

The drawing shows the variables when the program ends, and the program's 6 steps are listed below.

Line run 5
Next line end
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. celsius = [31.5, 24.0, 38.2, 29.9, 35.0] makes celsius a list of 5 items.
  3. Line 2. hot = filter(lambda c: c >= 30, celsius) sets hot to <filter object>. filter() has not tested a single temperature yet. It returns an object that will test each one when something asks for the next item.
  4. Line 3. first = list(hot) sets first to [31.5, 38.2, 35.0]. list() asks for every item, so the filter tests all five temperatures and hands out the three that are at least 30.
  5. Line 4. second = list(hot) sets second to []. The bug The filter object has nothing left to give, so the second list() gets no items. No error says so.
  6. Line 5. print(first, second) prints [31.5, 38.2, 35.0] []. That was the last line, so the program ends. To read the values twice, keep them in a list, first here, or build a new filter. A comprehension gives a list from the start.

That surprises people who replace a list comprehension with map() and then loop over the result twice: no error, just a second pass with nothing in it. The comprehensions give the same values as real lists, and many readers find them clearer as soon as a lambda would be needed. map() is at its best when the function already exists, as in map(int, "12 7 30".split()), a common way to read numbers from one line of input. filter(None, items) is a shortcut that keeps only the true items, dropping "", None and 0. The table sets the three ways to apply a function side by side; its second row is what the traced run above showed:

Applying a function to every item
Criterion map() and filter()List comprehensionGenerator expression
Gives you a lazy iteratora lista lazy iterator
Use the result twice No: the second pass is emptyYesNo: the second pass is empty
Filter and transform at once Needs both, nestedOne expression: [f(x) for x in xs if test(x)]The same, in parentheses
Reads best With an existing function: map(int, parts)With an expression you would write as a lambdaFed straight into sum(), max(), any() or a loop
When to choose The function exists already and one pass is enoughYou need the values more than once, or len() and indexingVery many items, one pass, little memory

itemgetter() and attrgetter() replace lambdas that only fetch

Lambdas that only fetch something, lambda s: s[1] or lambda p: p.rating, are so common that the operator module has ready-made versions. itemgetter(1) returns a function that gives back item 1 of whatever it receives, and attrgetter("rating") returns a function that reads the attribute rating. Given several positions or names, they return a tuple, which is again a ready-made multi-level key:

itemgetter() and attrgetter() as keys Python · itemgetter_attrgetter.py
from collections import namedtuple
from operator import attrgetter, itemgetter

scores = [("Asha", 82), ("Vikram", 91), ("Leela", 82), ("Imran", 77)]
print(sorted(scores, key=itemgetter(1), reverse=True))
print(max(scores, key=itemgetter(1)))

# With several items, itemgetter returns a tuple, which makes a sort key.
pick = itemgetter(1, 0)
print(pick(("Asha", 82)))
print(sorted(scores, key=pick))

Player = namedtuple("Player", "name rating")
players = [Player("Asha", 1520), Player("Ishaan", 1610), Player("Bela", 1475)]
print([p.name for p in sorted(players, key=attrgetter("rating"))])

# They describe themselves; a lambda only says it is a lambda.
print(itemgetter(1), attrgetter("rating"))
print((lambda s: s[1]).__name__)

Output

[('Vikram', 91), ('Asha', 82), ('Leela', 82), ('Imran', 77)]
('Vikram', 91)
(82, 'Asha')
[('Imran', 77), ('Asha', 82), ('Leela', 82), ('Vikram', 91)]
['Bela', 'Asha', 'Ishaan']
operator.itemgetter(1) operator.attrgetter('rating')
<lambda>

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 itemgetter_attrgetter.py

  • sorted(scores, key=itemgetter(1), reverse=True) sorts by the score, highest first; Asha and Leela both have 82 and keep their original order.
  • itemgetter(1, 0) builds the tuple (score, name), so ties are broken by name.
  • attrgetter works on objects, here a named tuple, and accepts dotted names such as "address.city" too.
  • Printed, they say exactly what they do: operator.itemgetter(1). A lambda only says <lambda>.

Each fetching lambda has an operator function that does the same job:

Fetching lambdas and the operator functions that replace them
Criterion The lambdaThe operator function
Item 1 lambda s: s[1]itemgetter(1)
Item 1, then item 0 lambda s: (s[1], s[0])itemgetter(1, 0)
An attribute lambda p: p.ratingattrgetter("rating")
A dotted attribute lambda p: p.address.cityattrgetter("address.city")
When to choose The key computes something: a negation, a length, a lower-case formThe key only fetches items or attributes

The Sorting HOWTO calls these functions easier to use and faster than the lambdas they replace. Still reach for a lambda when the key has to compute something: a negation, a length, a lower-case version of a string or a value from two fields.

Checkpoint

Check: map() and filter() hand out items once · itemgetter() and attrgetter()

  1. Question 1 of 2 What does this program print?

    Read the code, then choose one answer.

    evens = filter(lambda n: n % 2 == 0, [1, 2, 3, 4])
    print(len(list(evens)), len(list(evens)))
    Show the answer: Question 1 of 2

    Answer: 2 0

    filter() returns an iterator that tests each item when it is asked for the next one, and it never starts again. The first list() collects 2 and 4; the second gets an empty list, and no error says so.

  2. Question 2 of 2 With scores = [("Asha", 82), ("Leela", 82), ("Imran", 77)], what does sorted(scores, key=itemgetter(1, 0)) return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [('Imran', 77), ('Asha', 82), ('Leela', 82)]

    itemgetter(1, 0) is a function that returns (row[1], row[0]), the score and then the name. Tuples compare item by item, so 77 comes first, and the names order the two rows that scored 82.

A callback is a function that other code calls later

A callback: A function handed to other code, which calls it at the right moment, such as the function re.sub() calls for every match or the factory a defaultdict calls for a missing key. is a function you hand to some code so that it can call it at the right moment. Python’s standard library takes callbacks in many places, and a lambda keeps a short one next to the call that uses it:

Three callbacks Python · callbacks.py
import re
from collections import defaultdict

# re.sub calls the function back once for every match and uses what it returns.
text = "order 12 shipped in 3 boxes"
print(re.sub(r"\d+", lambda m: str(int(m.group()) * 2), text))

# defaultdict calls its factory whenever a missing key is read.
stock = defaultdict(lambda: "not stocked")
stock["tea"] = "12 packs"
print(stock["tea"], "|", stock["coffee"])

# min and max take the same key functions as sorted.
cities = {"Pune": 18.5, "Leh": -2.0, "Chennai": 29.1}
print(min(cities, key=cities.get), max(cities.items(), key=lambda kv: kv[1]))

Output

order 24 shipped in 6 boxes
12 packs | not stocked
Leh ('Chennai', 29.1)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 callbacks.py

  • re.sub() accepts a function instead of replacement text, calls it once for every match with the match object, and puts its return value in place of the match: every number in the sentence was doubled.
  • defaultdict calls its factory, here lambda: "not stocked", whenever a missing key is read with square brackets, as stock["coffee"] is, and stores the result; stock.get("coffee") never calls it.
  • min(cities, key=cities.get) passes a bound method as the key: the city with the smallest value wins. With max(cities.items(), key=lambda kv: kv[1]) you get the key and the value back together.

Step through the re.sub() call as messages between your code, re.sub() and the lambda it calls back:

Interactive · message by message re.sub() calling a lambda back for every match Step through the re.sub() call of callbacks.py. Each arrow is a call, or the value a call returns.
re.sub() calling a lambda back for every match, step 6 of 6. Messages so far: Your code to re.sub(): pattern, lambda, text; re.sub() to the lambda: the match for '12'; the lambda to re.sub(): '24'; re.sub() to the lambda: the match for '3'; the lambda to re.sub(): '6'; re.sub() to Your code: 'order 24 … 6 boxes'.Your codere.sub()the lambdapattern, lambda, textthe match for '12''24'the match for '3''6''order 24 … 6 boxes'

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Hand it over. Your code passes re.sub() three things: the pattern for a run of digits, the lambda itself, not called, and the text 'order 12 shipped in 3 boxes'.
  2. The first match. re.sub() finds the first run of digits, 12, and calls the lambda with its match object. Your code is not involved in this call.
  3. The replacement. The lambda reads the match with m.group(), doubles it and returns the text '24'. re.sub() puts '24' where '12' was.
  4. The second match. The next run of digits is 3, so re.sub() calls the lambda again, with a new match object.
  5. Another replacement. The lambda returns '6'. There are no more digits in the text, so re.sub() makes no more calls: two matches, two calls.
  6. The result. re.sub() returns the new string, and the program prints it. Your code handed over the lambda but never called it; re.sub() decided when and how often, which is what makes it a callback.

Your code never called the lambda: re.sub() did, twice, once for each number it found. Functions can also be returned. def adder(n): return lambda x: x + n hands back a new function on every call; adder(5)(10) is 15. How the returned lambda still knows n after adder has finished is the subject of the next lesson, on closures.

reduce() folds a sequence into one value

functools.reduce(function, iterable, initial) produces a fold: Combining a sequence into one value two items at a time: reduce(add, [3, 8, 1]) works out (3 + 8) + 1. sum(), max() and math.prod() are folds with names of their own.: it calls the two-argument function on the first two items, then on that result and the third item, and so on to the end. Given an initial value, it starts from that instead, which is also the answer for an empty sequence; without one, an empty sequence raises TypeError:

reduce() and the built-ins that do the same jobs Python · reduce_and_builtins.py
import math
import operator
from functools import reduce

nums = [3, 8, 1, 9, 4]

# reduce folds a sequence: ((((3 + 8) + 1) + 9) + 4)
print(reduce(operator.add, nums), sum(nums))
print(reduce(lambda a, b: a if a > b else b, nums), max(nums))
print(reduce(operator.mul, nums), math.prod(nums))

# The initial value is the result for an empty sequence (since 3.14 also by keyword).
print(reduce(operator.add, [], 0), reduce(operator.add, [], initial=0))
try:
    reduce(operator.add, [])
except TypeError as err:
    print("TypeError:", err)

# any and all stop early; reduce always reads everything.
print(any(n > 5 for n in nums), all(n > 0 for n in nums))

Output

25 25
9 9
864 864
0 0
TypeError: reduce() of empty iterable with no initial value
True True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 reduce_and_builtins.py

Step through the first fold of the program, reduce(operator.add, nums) on [3, 8, 1, 9, 4]:

Interactive · stage by stage reduce() folding [3, 8, 1, 9, 4] with addition Step through reduce(operator.add, nums). Each stage is one call of the function, on the result so far and the next item.
reduce() folding [3, 8, 1, 9, 4] with addition, step 5 of 5. Showing 3 + 8 = 11, 11 + 1 = 12, 12 + 9 = 21, 21 + 4 = 25, reduce() returns 25. In focus: reduce() returns 25.3 + 8 = 11the first two items11 + 1 = 12the result so far and 112 + 9 = 21the result so far and 921 + 4 = 25the last itemreduce() returns 25the same as sum(nums)

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The first call. Without an initial value, reduce() starts with the first two items and calls the function on them. operator.add(3, 8) returns 11.
  2. The next item. The result so far becomes the first argument of the next call, with the next item, 1, as the second. 11 + 1 is 12.
  3. And the next. The same again with 9. reduce() keeps only the result so far, never a list of partial results.
  4. The last item. 21 + 4 is 25. The list has no items left, so that was the last call, the fourth for five items.
  5. One value. reduce() returns 25, ((((3 + 8) + 1) + 9) + 4). sum(nums) gives the same answer and says what it does in its name, which is why it is the better choice here.

Four calls fold five items into 25, and every fold in that program has a clearer built-in: sum(), max() or math.prod(), while min() and, for strings, "".join() name two more common folds. any() and all() also stop at the first item that settles the answer, which reduce() never does. Use reduce() for a fold that has no built-in and reads naturally as “combine two things into one”, such as merging a list of dictionaries or composing functions.

Version note

Since Python 3.14, initial can also be passed by keyword, as in reduce(operator.add, [], initial=0), which the program does on its fourth line of output.

Checkpoint

Check: Callbacks · Folding with reduce()

  1. Question 1 of 2 How many times does re.sub(r"\d+", lambda m: str(int(m.group()) + 1), "rooms 4, 12 and 7") call the lambda?

    Type a number.

    A hint

    re.sub() calls the function once for every match.

    Show the answer: Question 1 of 2

    Answer: 3 calls

    The pattern matches three runs of digits, 4, 12 and 7, so re.sub() calls the lambda three times and builds "rooms 5, 13 and 8" from what it returns. Your code never calls the lambda itself.

  2. Question 2 of 2 What do reduce(operator.add, [], 0) and reduce(operator.add, []) do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The first returns 0; the second raises TypeError

    reduce() starts from the initial value when you give one, so an empty list simply returns it. Without one it must start from the first item, and an empty list has none, which raises TypeError. sum([]) is 0 without any help.

Practise: rank records and compose functions

The exercise asks for two functions: top_n() picks the best records by any key function, with ties ordered by name, and compose() builds one function out of several, applied from right to left. Write both in the editor; the sample tests check ties, empty results and built-in functions such as str and abs:

Exercise · Easy · Python

Pick the top records and compose functions

Write two functions in ranking.py:

  • top_n(records, n, key) returns a new list with the n records whose key(record) is highest, best first. Records with the same key are ordered by their "name" value, from A to Z. When n is larger than the number of records, return all of them in that order; when n is 0 or negative, return an empty list. The list passed in must not change. key can be any function, such as lambda r: r["points"].
  • compose(*funcs) returns a new function that applies the functions from right to left: compose(f, g, h)(x) is f(g(h(x))). With no functions at all, compose() returns a function that gives back its argument unchanged.

The tests sort players by their points, by the length of their names and with records that tie, and they compose built-in functions such as str and abs as well as lambdas.

Starter code · ranking.py

def top_n(records, n, key):
    """Return the n records with the highest key(record), best first; ties by "name"."""
    return records[:n]


def compose(*funcs):
    """Return a function that applies funcs from right to left."""
    def composed(value):
        return value
    return composed
The sample tests · test_ranking.py
from ranking import compose, top_n

PLAYERS = [
    {"name": "Meera", "points": 40},
    {"name": "Arjun", "points": 55},
    {"name": "Kabir", "points": 40},
    {"name": "Divya", "points": 55},
    {"name": "Sana", "points": 12},
]


def names(records):
    return [r["name"] for r in records]


def test_top_three():
    """returns the three best players, best first, ties from A to Z"""
    assert names(top_n(PLAYERS, 3, key=lambda r: r["points"])) == ["Arjun", "Divya", "Kabir"]


def test_n_larger_than_the_list():
    """returns every record when n is larger than the list"""
    assert names(top_n(PLAYERS, 10, key=lambda r: r["points"])) == ["Arjun", "Divya", "Kabir", "Meera", "Sana"]


def test_zero_or_negative_n():
    """returns an empty list when n is 0 or negative"""
    assert top_n(PLAYERS, 0, key=lambda r: r["points"]) == []
    assert top_n(PLAYERS, -2, key=lambda r: r["points"]) == []


def test_any_key_function():
    """works with any key, such as the length of the name"""
    assert names(top_n(PLAYERS, 2, key=lambda r: len(r["name"]))) == ["Arjun", "Divya"]
    assert names(top_n(PLAYERS, 1, key=lambda r: -r["points"])) == ["Sana"]


def test_input_unchanged():
    """does not change the list it was given"""
    before = [dict(p) for p in PLAYERS]
    top_n(PLAYERS, 2, key=lambda r: r["points"])
    assert PLAYERS == before


def test_compose_right_to_left():
    """applies the functions from right to left"""
    inc = lambda x: x + 1
    double = lambda x: x * 2
    assert compose(inc, double)(5) == 11
    assert compose(double, inc)(5) == 12
    assert compose(str, abs, int)("-42") == "42"


def test_compose_nothing():
    """compose() gives back its argument unchanged"""
    same = compose()
    assert same(7) == 7
    assert same("text") == "text"
A hint

sorted() returns a new list and never changes the one you give it, and its sorts are stable. So sort by name first, then sort that result by key with reverse=True: records with the same key keep their A-to-Z order. A slice such as [:n] returns at most n items. For compose, the inner function can loop over reversed(funcs), passing each result on to the next function.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Try your own sort keys on bigger data, or paste in rows of your own, in a full Python editor in the browser.

Then test yourself on keys, iterators, callbacks and folds. The session picks the idea you are least sure of, asks a harder question once you get one right, and explains every choice you make:

Practice that adapts to you

Practise this lesson

20 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 20 Which call sorts the list words by the length of each word?

    Choose one answer.

    Show the answer: Question 1 of 20

    Answer: sorted(words, key=len)

    A function is a value, and its name without parentheses refers to it. key=len hands sorted() the function, and sorted() decides when to call it: once per word.

  2. Question 2 of 20 What does this program print?

    Read the code, then choose one answer.

    def apply_twice(func, value):
        return func(func(value))
    
    print(apply_twice(lambda n: n * 3, 2))
    Show the answer: Question 2 of 20

    Answer: 18

    func refers to the lambda. func(value) is 2 * 3 = 6, and func(func(value)) is 6 * 3 = 18. apply_twice() is a higher-order function: it takes a function and decides when to call it.

  3. Question 3 of 20 Why does PEP 8 ask for def instead of parity = lambda n: n % 2?

    Choose one answer.

    Show the answer: Question 3 of 20

    Answer: Because the def function gets the name parity, which tracebacks and its repr then show, where a lambda is only <lambda>

    A lambda creates the same kind of function object as def, just as fast and just as reusable. Assigning it to a name gains nothing over def and loses the name: tracebacks, profiles and repr() show <lambda> instead of parity.

  4. Question 4 of 20 What does this program print?

    Read the code, then choose one answer.

    pairs = [(3, 1), (1, 2), (2, 0)]
    print(sorted(pairs, key=lambda p, i=1: p[i]))
    Show the answer: Question 4 of 20

    Answer: [(2, 0), (3, 1), (1, 2)]

    A lambda's parameters follow the same rules as a def's, so i=1 is a default value. The key returns p[1] for each pair, 1, 2 and 0, and the pairs come out in that order: (2, 0), (3, 1), (1, 2).

  5. Question 5 of 20 report.py uses five lambdas as sort keys, and a traceback ends in "line 12, in <lambda>". What change makes the next traceback name the function that failed?

    Choose one answer.

    Show the answer: Question 5 of 20

    Answer: Make that key a def with a name, such as def by_penalty(row):

    Every lambda has the name <lambda>, whatever it is assigned to, so a traceback cannot tell five lambdas apart. Once a function is worth finding in a traceback, it is worth a def with a name.

  6. Question 6 of 20 sorted(rows, key=lambda r: r["city"]) sorts a list of 1,000 rows. How many times does Python call the lambda?

    Type a number.

    Show the answer: Question 6 of 20

    Answer: 1000 calls

    The key function is called exactly once for each item, as the logging key of leaderboard.py showed for six rows. The sort then compares the 1,000 keys it has, however many comparisons that takes.

  7. Question 7 of 20 Each student is a dict with "name" and "marks". Which key sorts them by marks, highest first, and students with equal marks by name from A to Z?

    Choose one answer.

    Show the answer: Question 7 of 20

    Answer: key=lambda s: (-s["marks"], s["name"])

    The sort is smallest first, so negating the marks puts the highest first, and the name breaks ties in A-to-Z order. reverse=True would reverse the names too, adding a number to a string raises TypeError, and the last choice computes a tuple once, before sorted() runs, instead of passing a function.

  8. Question 8 of 20 Some products have "price": None, and sorted(products, key=lambda p: p["price"]) raises TypeError, because None cannot be compared with a number. Which key puts the products without a price last and sorts the others by price?

    Choose one answer.

    A hint

    A tuple's first item can sort the rows into two groups.

    Show the answer: Question 8 of 20

    Answer: key=lambda p: (p["price"] is None, p["price"] or 0)

    Every key must compare with every other. The tuple (p["price"] is None, p["price"] or 0) first splits the products into priced (False) and unpriced (True), then compares prices only among the priced ones, so None never meets a number.

  9. Question 9 of 20 What does it mean that Python's sort is stable?

    Choose one answer.

    Show the answer: Question 9 of 20

    Answer: Items with equal keys keep the order they had before the sort

    A stable sort keeps items with equal keys in their original order. sorted() and list.sort() are both stable, and reverse=True keeps that promise, so sorting by a minor key and then by a major key orders on both.

  10. Question 10 of 20 What does this program print?

    Read the code, then choose one answer.

    rows = [("Kabir", 4), ("Arjun", 5), ("Meera", 4)]
    by_name = sorted(rows, key=lambda r: r[0])
    print(sorted(by_name, key=lambda r: r[1], reverse=True))
    Show the answer: Question 10 of 20

    Answer: [('Arjun', 5), ('Kabir', 4), ('Meera', 4)]

    The first sort orders by name: Arjun, Kabir, Meera. The second orders by the number, highest first, and because it is stable the two rows with 4 stay in name order: Arjun, Kabir, Meera.

  11. Question 11 of 20 What does this program print?

    Read the code, then choose one answer.

    print(list(filter(None, [0, "", "a", None, 3, []])))
    Show the answer: Question 11 of 20

    Answer: ['a', 3]

    filter(None, items) keeps every item that is true, so 0, "", None and [] are dropped and 'a' and 3 stay. list() then collects them from the filter object.

  12. Question 12 of 20 What does this print?

    Read the code, then choose one answer.

    doubled = map(lambda n: n * 2, [1, 2, 3])
    print(sum(doubled), sum(doubled))
    Show the answer: Question 12 of 20

    Answer: 12 0

    map() returns an iterator, which hands out its items only once. The first sum() reads 2, 4 and 6 and gets 12; the second finds the iterator used up and adds nothing, so it returns 0. A list comprehension, [n * 2 for n in [1, 2, 3]], would give 12 both times.

  13. Question 13 of 20 A function returns map(clean, rows). Its caller calls len() on the result and then loops over it twice. What should the function return instead?

    Choose one answer.

    Show the answer: Question 13 of 20

    Answer: [clean(r) for r in rows], a list

    map(), filter() and generator expressions are one-shot iterators without a length. Use them for one pass over a large input; return a list when callers need len(), indexing or several passes.

  14. Question 14 of 20 Which lambda does the same as operator.itemgetter(2, 0)?

    Choose one answer.

    Show the answer: Question 14 of 20

    Answer: lambda row: (row[2], row[0])

    Given several positions, itemgetter returns a function that builds a tuple of those items, in the order you named them. As a sort key, that tuple sorts by item 2 first and breaks ties with item 0.

  15. Question 15 of 20 What does this program print?

    Read the code, then choose one answer.

    from collections import namedtuple
    from operator import attrgetter
    
    City = namedtuple("City", "name temp")
    cities = [City("Leh", -2.0), City("Pune", 18.5), City("Chennai", 29.1)]
    print(max(cities, key=attrgetter("temp")).name)
    Show the answer: Question 15 of 20

    Answer: Chennai

    attrgetter("temp") is a function that returns city.temp. max() calls it on each city and returns the city whose key is largest, City("Chennai", 29.1), and .name gives Chennai.

  16. Question 16 of 20 What does callbacks.py print?

    What does this program print? Choose one answer.

    import re
    from collections import defaultdict
    
    # re.sub calls the function back once for every match and uses what it returns.
    text = "order 12 shipped in 3 boxes"
    print(re.sub(r"\d+", lambda m: str(int(m.group()) * 2), text))
    
    # defaultdict calls its factory whenever a missing key is read.
    stock = defaultdict(lambda: "not stocked")
    stock["tea"] = "12 packs"
    print(stock["tea"], "|", stock["coffee"])
    
    # min and max take the same key functions as sorted.
    cities = {"Pune": 18.5, "Leh": -2.0, "Chennai": 29.1}
    print(min(cities, key=cities.get), max(cities.items(), key=lambda kv: kv[1]))
    Show the answer: Question 16 of 20

    Answer: it prints

    order 24 shipped in 6 boxes
    12 packs | not stocked
    Leh ('Chennai', 29.1)

    re.sub() calls the lambda for each number and doubles it; the defaultdict calls its factory for the missing key "coffee"; min() with key=cities.get picks the city with the lowest value, and max() over the items returns the pair with the highest value.

  17. Question 17 of 20 When does defaultdict(lambda: "not stocked") call its lambda?

    Choose one answer.

    Show the answer: Question 17 of 20

    Answer: Whenever code reads a missing key with square brackets, as in stock["coffee"]

    defaultdict keeps the function you give it and calls it back whenever a missing key is read with square brackets: stock["coffee"] gets "not stocked", and that value is stored, while stock.get("coffee") returns None. That is a callback: your code hands over the function, and other code decides when to call it.

  18. Question 18 of 20 A button library calls on_click with no arguments when the button is pressed. Which of these makes the button call show("Saved")?

    Choose one answer.

    Show the answer: Question 18 of 20

    Answer: on_click=lambda: show("Saved")

    A callback must be a function that the library can call the way it promises, here with no arguments. A lambda with no parameters wraps the call you want and runs it only when the button is pressed.

  19. Question 19 of 20 Which built-in does the same as reduce(lambda a, b: a if a > b else b, nums)?

    Choose one answer.

    Show the answer: Question 19 of 20

    Answer: max(nums)

    reduce() calls the lambda on the first two items, then on the larger of them and the next item, and so on, so the largest item survives. max(nums) says that in three letters and also accepts a key.

  20. Question 20 of 20 When is functools.reduce() the clearest choice?

    Choose one answer.

    Show the answer: Question 20 of 20

    Answer: For combining items two at a time when no built-in names the job, such as merging a list of dictionaries

    reduce() folds a sequence two items at a time. When a built-in such as sum(), max(), math.prod(), any(), all() or "".join() does the fold, use it; keep reduce() for folds that have no name of their own.

Interview questions

Fresher · What is the difference between a lambda and a function defined with def?

Both create ordinary function objects: a lambda can be passed, stored and called exactly like a function made with def, and it sees the variables around it in the same way. The difference is in what you can write and in the name. A lambda’s body is one expression whose value is returned, so it cannot hold statements such as assignments, loops, return, raise or try, and its parameters cannot have annotations. And a lambda has no name of its own: its __name__ is <lambda>, which is all a traceback or a profiler can show. So a lambda suits a short function passed straight into a call, such as a sort key or a callback, and def suits anything you name, reuse, document or test. PEP 8 asks for def whenever a lambda would be assigned to a name.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    In a code review, valid = [parse(r) for r in rows if r] became valid = map(parse, filter(None, rows)). The tests pass, but a report that loops over valid twice now prints an empty second section. Explain what happened and how you would review such a change.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    You design run_pipeline(records, steps), where steps is a list of functions applied to each record in turn, some of them lambdas. A step fails on record 41,207 of 2 million and the traceback only says <lambda>. How do you make failures name the step and the record, and keep the steps easy to test?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A function is a value: pass it by its name, without parentheses, and the code you pass it to decides when to call it.
  • lambda params: expression makes a function with one expression for a body and the name <lambda>; use it inline, and use def for anything that deserves a name.
  • sorted() calls its key once per item and compares the keys, so a tuple key sorts on several criteria and negation reverses a number.
  • Python’s sort is stable, so sorting by the minor key and then by the major key orders text in both directions.
  • map() and filter() return iterators that are used up after one pass; a comprehension gives a list, and itemgetter() and attrgetter() replace lambdas that only fetch.
  • A callback is a function other code calls for you, and functools.reduce() folds a sequence, where sum(), max(), math.prod(), any() and all() usually say the same thing more clearly.

Terms in this lesson

callback
A function handed to other code, which calls it at the right moment, such as the function re.sub() calls for every match or the factory a defaultdict calls for a missing key.
fold
Combining a sequence into one value two items at a time: reduce(add, [3, 8, 1]) works out (3 + 8) + 1. sum(), max() and math.prod() are folds with names of their own.
higher-order function
A function that takes another function as an argument, returns one, or both. sorted() is one: it calls the key function you pass once for every item.
key function
A function that sorted(), list.sort(), min() and max() call once for every item; they then compare its results instead of the items. key=len compares the lengths.
lambda
An expression that makes a small function without a name: lambda r: r[1] takes r and returns r[1]. Its body is one expression, never a statement, and its name is always <lambda>.
stable sort
A sort that keeps items with equal keys in the order they came in. Python's sorted() and list.sort() are stable, so a second sort keeps the order of the first among ties.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.