← Back to Python series
⚙️
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 age

2. 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())  # 3

3. *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))  # 49

5. Common Mistakes

  1. Lambda with multiple statements — not allowed. If you need multiple lines, write a regular def.
  2. Late binding in closures: all closures in a loop capture the same variable. Use default arg: lambda i=i: i.
  3. 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.pyMulti-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=21
02_closure_adder.pyClosure 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
169
Exercise 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 ↗