Ana wants to see the Papyrus catalog sorted by price, to know which book is the cheapest, and to filter the ones that are in stock. All those operations have something in common: a main function (sorted(), min(), a filter) needs you to tell it what criterion to work with — and that criterion is usually a tiny, single-expression function that doesn't deserve a def with a name, a docstring and its own slot in the file. For those cases Python offers lambda functions: anonymous one-line functions written right where they're used. In this lesson you'll learn their syntax, when they're a good idea and when they're not (PEP 8 has a firm opinion), and you'll put them to work with sorted(), min()/max(), map() and filter() — comparing the last two with the comprehensions from module 2, which often beat them.

Contents

  1. Syntax: anatomy of a lambda
  2. Lambda versus def: when to and when not to
  3. The key parameter: sorting with sorted()
  4. min() and max() with a criterion
  5. map() and filter() versus comprehensions
  6. Project: the Papyrus catalog sorted by price

Syntax: anatomy of a lambda

A lambda is a nameless function reduced to its bare minimum:

lambda parameters: expression

Let's compare it with its def equivalent:

def price_with_vat(base):
    return round(base * 1.04, 2)

# The same logic as a lambda:
lambda base: round(base * 1.04, 2)

Piece-by-piece correspondence:

def lambda
def price_with_vat(base): lambda base:
Function name It has none (it's anonymous)
Multi-line body, with if, loops, etc. A single expression
Explicit return Implicit: the expression IS the returned value
Docstring Not allowed

Key restriction: the body must be one expression — something that produces a value. Operations, function calls, f-strings and the ternary from module 2 ("out of stock" if stock == 0 else "available") all fit, but not statements: no assignments, no while, no for as a loop, no multiple lines. If you need those, you need a def.

A lambda can take several parameters (lambda title, price: f"{title}: {price} EUR") and even default values, although in practice the vast majority take one or two.

Lambda versus def: when to and when not to

You can run a lambda directly, and even store it in a variable:

print((lambda base: round(base * 1.04, 2))(12.50))   # 13.0 — legal, but contrived

with_vat = lambda base: round(base * 1.04, 2)         # legal, but PEP 8 says NO

That second line works... and PEP 8 explicitly discourages it: if you're going to give it a name, use def. The reasons are practical:

  • With def with_vat(base): ..., errors and debugging tools show the name with_vat; an assigned lambda shows up as the anonymous <lambda>, harder to track down.
  • def allows a docstring, multiple lines, and grows without rewrites.
  • You gain nothing: with_vat = lambda ... takes up as much room as the def and communicates less.

So what are they for? For passing as an argument to another function, written at the exact point where they're used. That's their home turf:

Situation Tool
One-line criterion for sorted/min/max/map/filter, used only once lambda
The same logic is used in two or more places named def
The criterion needs a real if/else, validation or several lines def
You want to document what it does def (docstring)
Assigning it to a name "to save typing" Never: def

That a function can be passed as an argument shouldn't surprise you at this point: in Python functions are values, like numbers or strings. find_book (without parentheses, lesson 03-01) is a value that names a function; a lambda is the same thing, but without going through a name.

The key parameter: sorting with sorted()

sorted(sequence) returns a new sorted list (the original doesn't change):

print(sorted(prices))    # [9.95, 12.5, 15.9]
print(sorted(catalog))   # ['Don Quixote', 'Hamlet', 'The Odyssey'] (alphabetical order)

But Ana doesn't want to sort loose prices or bare titles: she wants to sort books by price. First we pair up title and price with zip() (previous lesson) and then the keyword argument key comes in: a function that, applied to each element, produces the value to sort by.

books = list(zip(catalog, prices))
# [('The Odyssey', 12.5), ('Hamlet', 9.95), ('Don Quixote', 15.9)]

by_price = sorted(books, key=lambda book: book[1])
# [('Hamlet', 9.95), ('The Odyssey', 12.5), ('Don Quixote', 15.9)]

How it works on the inside: for each tuple book, sorted() calls the lambda, gets book[1] (the price) and sorts the tuples by comparing those values. The lambda doesn't sort anything; it only answers the question "which of your data should I compare you by?".

Useful variants:

priciest_first = sorted(books, key=lambda book: book[1], reverse=True)   # descending
alphabetical = sorted(catalog, key=lambda t: t.lower())                  # ignores case

reverse=True flips the order — another keyword argument, like the ones from lesson 03-02. And key=lambda t: t.lower() solves the classic problem of capital letters disturbing alphabetical order.

min() and max() with a criterion

min() and max() accept the same key, and answer business questions in one line:

cheapest = min(books, key=lambda book: book[1])
most_expensive = max(books, key=lambda book: book[1])

print(f"Today's bargain: {cheapest[0]} at {cheapest[1]:.2f} EUR")
# Today's bargain: Hamlet at 9.95 EUR
print(f"The shop's gem: {most_expensive[0]} at {most_expensive[1]:.2f} EUR")
# The shop's gem: Don Quixote at 15.90 EUR

Important detail: they return the whole element (the title-price tuple), not the value of the criterion. That's why cheapest[0] gives the title. Without key, min(prices) would give 9.95 — the price, but with no idea which book it belongs to.

map() and filter() versus comprehensions

Two classic functions that take another function as an argument:

  • map(function, sequence): applies the function to every element (transforms).
  • filter(function, sequence): keeps the elements for which the function returns something true (sifts).

Both are lazy, like zip(): they deliver results as they're requested, and list() materializes them.

# Transform: member prices for the whole catalog
list(map(lambda base: final_price(base, member=True), prices))
# [12.35, 9.83, 15.71]

# Sift: prices under 13 EUR
list(filter(lambda base: base < 13, prices))
# [12.5, 9.95]

Do these operations ring a bell? They're exactly what you did in module 2 with comprehensions:

[final_price(base, member=True) for base in prices]   # map
[base for base in prices if base < 13]                # filter

An honest comparison:

map()/filter() + lambda Comprehension
Readability with custom logic So-so: the lambda adds noise High: it reads like a sentence
With an already existing function Very good: map(str.strip, lines) Good: [l.strip() for l in lines]
Transforming and sifting at once Clumsy: you have to nest map(f, filter(g, x)) Natural: [f(x) for x in xs if g(x)]
Result Lazy (you have to call list()) A list, directly
Dominant style in modern Python Fading Recommended

Policy for this course: use comprehensions by default; reach for map()/filter() when the function already exists and has a name (there you don't even need a lambda). Even so, you must be able to read them: you'll run into them constantly in other people's code.

Project: the Papyrus catalog sorted by price

Let's put it all together in price_list.py: the catalog table sorted by price, with Luis's member prices and a small report:

BOOK_VAT = 0.04
MEMBER_DISCOUNT = 0.05

catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
prices = [12.50, 9.95, 15.90]
stocks = [4, 6, 8]


def final_price(base, member=False, vat=BOOK_VAT):
    """Final price: member discount (if applicable) and then VAT."""
    if member:
        base = base * (1 - MEMBER_DISCOUNT)
    return round(base * (1 + vat), 2)


books = list(zip(catalog, prices, stocks))
by_price = sorted(books, key=lambda book: book[1])

print(f"{'Title':<12} {'Base':>7} {'Member':>7}")
print("-" * 28)
for title, base, stock in by_price:
    print(f"{title:<12} {base:>5.2f} EUR {final_price(base, member=True):>5.2f} EUR")

cheapest = min(books, key=lambda book: book[1])
print(f"\nBudget recommendation: {cheapest[0]} ({cheapest[1]:.2f} EUR)")
Title           Base  Member
----------------------------
Hamlet        9.95 EUR  9.83 EUR
The Odyssey  12.50 EUR 12.35 EUR
Don Quixote  15.90 EUR 15.71 EUR

Budget recommendation: Hamlet (9.95 EUR)

Review the division of labor: zip() packs the parallel lists into per-book tuples, the key lambda points to the price as the criterion, sorted() sorts without touching the original data, and final_price() — our named function, because it's reused — computes the member column. Every tool in its place.

Common Mistakes and Tips

  • Assigning lambdas to names (f = lambda x: ...): it works, but PEP 8 vetoes it and error tracebacks will show <lambda> instead of a useful name. Use def.
  • Trying to put statements in a lambda (lambda x: y = x + 1 or multiple lines): SyntaxError. A lambda is an expression; if it doesn't fit, it's a def.
  • Passing the called lambda instead of the lambda: sorted(books, key=book[1]) fails — key expects a function, not a value. The correct form is key=lambda book: book[1].
  • Forgetting list() with map()/filter(): print(map(...)) shows <map object at 0x...>, not the data. Materialize it with list() or traverse it with for.
  • Mile-long lambdas: if your lambda has a ternary inside another one or runs past half a line, nobody will understand it tomorrow. Extract a def with a name and a docstring.
  • Believing sorted() modifies the list: it returns a new list. (Lists also have a .sort() method that does sort in place; we'll see it in module 4.)
  • Tip: write the lambda with the sentence "for each element, compare me by ___" in mind. Whatever fills the blank is the body of your lambda.

Exercises

Exercise 1: the shelf sorted by stock

With books = list(zip(catalog, prices, stocks)) (include "Faust" at 21.00 EUR with stock 0: build it with catalog + ["Faust"], etc.), print the books sorted from least to most stock, one line per book in the format <title>: N units. Then use max() with key to print which book has the most copies in stock.

Exercise 2: map/filter versus comprehensions

Starting from prices = [12.50, 9.95, 15.90, 21.00]: (a) use map() and a lambda to get the list of member prices (use final_price(base, member=True)); (b) use filter() and a lambda to get the base prices above 12 EUR; (c) rewrite both as comprehensions; (d) write in a single comprehension "member prices of the books whose base price is above 12 EUR" and explain why map+filter would be less readable.

Exercise 3: sorting titles by length

Ana wants the shelf labels sorted by title length (shortest first) and, for equal lengths, alphabetically. Hint: the lambda can return a tuple (criterion1, criterion2) — Python compares tuples element by element, as you saw when comparing strings. Apply it to ["Don Quixote", "Hamlet", "Faust", "The Odyssey"].

Solutions

Exercise 1:

titles = catalog + ["Faust"]
bases = prices + [21.00]
units = stocks + [0]
books = list(zip(titles, bases, units))

for title, base, stock in sorted(books, key=lambda book: book[2]):
    print(f"{title}: {stock} units")
# Faust: 0 units / The Odyssey: 4 units / Hamlet: 6 units / Don Quixote: 8 units

top = max(books, key=lambda book: book[2])
print(f"Most copies in stock: {top[0]} ({top[2]} units)")   # Don Quixote (8 units)

The stock is element [2] of each tuple; changing the sort criterion is a matter of changing an index in the lambda.

Exercise 2:

# (a) and (b)
member_prices = list(map(lambda base: final_price(base, member=True), prices))
# [12.35, 9.83, 15.71, 20.75]
pricey = list(filter(lambda base: base > 12, prices))
# [12.5, 15.9, 21.0]

# (c) equivalent comprehensions
member_prices = [final_price(base, member=True) for base in prices]
pricey = [base for base in prices if base > 12]

# (d) transform and sift at once
pricey_member_prices = [final_price(base, member=True) for base in prices if base > 12]
# [12.35, 15.71, 20.75]

With map+filter, version (d) would be list(map(lambda b: final_price(b, member=True), filter(lambda b: b > 12, prices))): two lambdas, two nested calls, and it reads from the inside out. The comprehension expresses the same thing left to right, like a sentence.

Exercise 3:

titles = ["Don Quixote", "Hamlet", "Faust", "The Odyssey"]
in_order = sorted(titles, key=lambda t: (len(t), t))
print(in_order)   # ['Faust', 'Hamlet', 'Don Quixote', 'The Odyssey']

The lambda returns (length, title): Python sorts by length first and uses the title as the tiebreaker. "Faust" (5 letters) and "Hamlet" (6) need no tiebreak; "Don Quixote" and "The Odyssey" tie at 11 characters (the space counts) and alphabetical order settles it — "Don Quixote" before "The Odyssey". Without the second element of the tuple, the order between the two 11-character titles would depend on their original position.

Conclusion

Lambdas complete your function toolbox: the syntax lambda params: expression, a single-expression body with an implicit return, and a well-fenced territory — throwaway criteria for sorted(), min(), max(), map() and filter(), never substitutes for def when logic deserves a name (so says PEP 8). Along the way, the Papyrus catalog now sorts by price, by stock, or by whatever a one-line lambda says, and you know when a comprehension expresses the same thing more clearly. But there's a growing problem: final_price() is copied into price_list.py, and find_book() lives locked inside menu.py. We write functions to reuse them... and we keep copying them from file to file. The next lesson solves exactly that: modules and packages — we'll create papyrus_utils.py, the shop's first module of its own, and any script will be able to import its functions with a single line.

Python Programming Course

Module 1: Introduction to Python

Module 2: Control Structures

Module 3: Functions and Modules

Module 4: Data Structures

Module 5: Object-Oriented Programming

Module 6: File Handling

Module 7: Error and Exception Handling

Module 8: Advanced Topics

Module 9: Testing and Debugging

Module 10: Web Development with Python

Module 11: Data Science with Python

Module 12: Final Project

© Copyright 2026. All rights reserved