⚙️
Intermediate
lambda · Closures · *args · **kwargs · functools
Week 1 — Advanced Functions
Go beyond basic def: write anonymous functions with lambda, understand closures, handle variable-length arguments, and use functools tools like partial and lru_cache.
lambdaclosureargskwargsfunctoolspartial
Duration
⏱ 2.5 hours
Level
📊 Intermediate
Prerequisite
🎯 Basic track complete
OUTCOME
Write higher-order functions and use functools.lru_cache for memoization
What you'll learn
- 1Write lambda expressions for short anonymous functions
- 2Explain how closures capture enclosing scope
- 3Accept variable positional (*args) and keyword (**kwargs) arguments
- 4Use functools.partial and functools.lru_cache
- 5Pass functions as arguments to map, filter, sorted
1. Lambda Expressions
lambda creates an anonymous function in a single expression. Best used for short, one-off operations.
python
square = lambda x: x ** 2
add = lambda a, b: a + b
print(square(5)) # 25
print(add(3, 4)) # 7
# Common use: sort key
people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
people.sort(key=lambda p: p[1])
print(people) # sorted by age2. Closures
A closure is a function that remembers variables from its enclosing scope, even after that scope has finished.
python
def make_counter(start=0):
count = start
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 33. *args and **kwargs
python
def my_sum(*args):
return sum(args)
print(my_sum(1, 2, 3, 4)) # 10
def log_event(event, **kwargs):
print(f"[{event}]", " ".join(f"{k}={v}" for k, v in kwargs.items()))
log_event("login", user="alice", ip="127.0.0.1")4. functools
python
from functools import lru_cache, partial
@lru_cache(maxsize=None)
def fib(n):
if n <= 1: return n
return fib(n-1) + fib(n-2)
print(fib(50)) # fast thanks to memoization
double = partial(pow, exp=2) # pow with exp fixed to 2
# Note: partial works with positional; use lambda for keyword only
double = lambda x: x ** 2
print(double(7)) # 495. Common Mistakes
- Lambda with multiple statements — not allowed. If you need multiple lines, write a regular def.
- Late binding in closures: all closures in a loop capture the same variable. Use default arg: lambda i=i: i.
- Forgetting nonlocal when modifying an enclosing variable — Python treats it as a new local otherwise.
💻 Examples
Run these examples and check the output yourself.
01_lambda_sort.py— Multi-key sort with lambda
CODE
students = [
{"name": "Alice", "grade": 90, "age": 20},
{"name": "Bob", "grade": 90, "age": 22},
{"name": "Carol", "grade": 85, "age": 21},
]
# Sort by grade descending, then by age ascending
students.sort(key=lambda s: (-s["grade"], s["age"]))
for s in students:
print(f"{s['name']}: grade={s['grade']}, age={s['age']}")
▶ Output
Alice: grade=90, age=20
Bob: grade=90, age=22
Carol: grade=85, age=2102_closure_adder.py— Closure factory: make_adder
CODE
def make_adder(n):
def adder(x):
return x + n
return adder
add5 = make_adder(5)
add10 = make_adder(10)
print(add5(3)) # 8
print(add10(3)) # 13
print(list(map(add5, [1, 2, 3]))) # [6, 7, 8]
▶ Output
8
13
[6, 7, 8]03_variadic.py— *args and **kwargs in practice
CODE
def report(*values, sep=", ", label="Result"):
print(f"{label}: {sep.join(str(v) for v in values)}")
report(1, 2, 3)
report("a", "b", "c", sep=" | ", label="Letters")
▶ Output
Result: 1, 2, 3
Letters: a | b | c📝 Exercises
Try them yourself first, then open the solution to compare.
Exercise 1
Pipeline
Goal: Write apply_pipeline(value, *functions) that applies each function in sequence.
Requirements
- Takes a value and any number of functions
- Returns result of applying all functions left to right
- Test with a pipeline of lambda math operations
▶Toggle solution
SOLUTION
from functools import reduce
def apply_pipeline(value, *funcs):
return reduce(lambda v, f: f(v), funcs, value)
result = apply_pipeline(5,
lambda x: x * 2, # 10
lambda x: x + 3, # 13
lambda x: x ** 2, # 169
)
print(result) # 169
▶ Output
169Exercise 2
Memoized Fibonacci
Goal: Implement Fibonacci with lru_cache and compare performance.
Requirements
- Plain recursive fib vs @lru_cache version
- Time both for n=35 with time.perf_counter
- Print results and elapsed time
▶Toggle solution
SOLUTION
import time
from functools import lru_cache
def fib_slow(n):
if n <= 1: return n
return fib_slow(n-1) + fib_slow(n-2)
@lru_cache
def fib_fast(n):
if n <= 1: return n
return fib_fast(n-1) + fib_fast(n-2)
n = 35
t0 = time.perf_counter()
print(fib_slow(n), f'slow: {time.perf_counter()-t0:.4f}s')
t0 = time.perf_counter()
print(fib_fast(n), f'fast: {time.perf_counter()-t0:.6f}s')
Example code / lecture materials
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗