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
- Syntax: anatomy of a lambda
- Lambda versus
def: when to and when not to - The
keyparameter: sorting withsorted() min()andmax()with a criterionmap()andfilter()versus comprehensions- Project: the Papyrus catalog sorted by price
Syntax: anatomy of a lambda
A lambda is a nameless function reduced to its bare minimum:
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 NOThat 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 namewith_vat; an assigned lambda shows up as the anonymous<lambda>, harder to track down. defallows a docstring, multiple lines, and grows without rewrites.- You gain nothing:
with_vat = lambda ...takes up as much room as thedefand 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 casereverse=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 EURImportant 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] # filterAn 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. Usedef. - Trying to put statements in a lambda (
lambda x: y = x + 1or multiple lines):SyntaxError. A lambda is an expression; if it doesn't fit, it's adef. - Passing the called lambda instead of the lambda:
sorted(books, key=book[1])fails —keyexpects a function, not a value. The correct form iskey=lambda book: book[1]. - Forgetting
list()withmap()/filter():print(map(...))shows<map object at 0x...>, not the data. Materialize it withlist()or traverse it withfor. - 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
defwith 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
- Introduction to Python
- Setting Up the Development Environment
- Python Syntax and Basic Data Types
- Variables and Constants
- Basic Input and Output
- Virtual Environments and Package Management
Module 2: Control Structures
Module 3: Functions and Modules
- Defining Functions
- Function Arguments
- Lambda Functions
- Modules and Packages
- Standard Library Overview
Module 4: Data Structures
Module 5: Object-Oriented Programming
Module 6: File Handling
Module 7: Error and Exception Handling
- Introduction to Exceptions
- Handling Exceptions
- Raising Exceptions
- Custom Exceptions
- Best Practices and Error Logging
Module 8: Advanced Topics
- Type Hints
- Decorators
- Generators
- Context Managers
- Concurrency: Threads and Processes
- Asyncio for Asynchronous Programming
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with unittest
- Testing with pytest
- Test-Driven Development
- Debugging Techniques
- Using pdb for Debugging
Module 10: Web Development with Python
- Introduction to Web Development
- Flask Framework Fundamentals
- Building REST APIs with Flask
- Introduction to Django
- Building Web Applications with Django
Module 11: Data Science with Python
- Introduction to Data Science
- NumPy for Numerical Computing
- Pandas for Data Manipulation
- Matplotlib for Data Visualization
- Introduction to Machine Learning with scikit-learn
