Functions, Scope, Closures & Functional Programming
Functions Are First-Class Objects
In Python, functions are first-class objects — they are values just like integers or strings. You can assign a function to a variable, store it in a list, pass it as an argument, and return it from another function. This is not a trick or an edge case; it is a core design principle of Python that unlocks enormous expressive power.
def, return, and the Implicit None
Every Python function returns a value. If you do not write a return statement (or write return with no value), Python implicitly returns None. This is a common source of bugs — assigning the result of a function that forgets to return.
Docstrings — PEP 257
Docstrings are string literals placed immediately after the function definition. They become the __doc__ attribute and are shown by help(). Write them for every public function.
All Parameter Types — A Complete Guide
Python has five distinct parameter types. Understanding the rules for combining them prevents confusing TypeError messages.
Positional and Keyword Arguments
Default Values — The Mutable Default Trap
Default values are evaluated once when the def statement executes — NOT each time the function is called. Mutable defaults (lists, dicts) are shared across all calls. This is one of Python's most famous gotchas.
*args and **kwargs
*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dict.
Keyword-Only and Positional-Only Parameters
Python 3 added syntax to force certain parameters to be passed only as keywords (after *) or only as positionals (before /):
Scope and the LEGB Rule
When Python encounters a name like x, it searches for it in a specific order: Local → Enclosing → Global → Built-in. The first match wins.
global and nonlocal Statements
Without a declaration, assignment inside a function creates a local variable, even if a global with the same name exists. Use global or nonlocal to modify outer variables.
Closures — The Deep Dive
A closure is a function that remembers the variables from the scope in which it was created, even after that scope has finished executing. It "closes over" those free variables, keeping them alive in special cell objects.
The Loop Variable Capture Gotcha and Its Fix
Closures capture variables, not values. In a loop, all closures share the same loop variable — after the loop ends, they all see its final value.
Lambda Functions
Lambdas are anonymous single-expression functions. They are syntactically restricted: one expression, no statements, no assignments. Use them when a full def would be overkill — primarily as the key= argument to sort functions or with map/filter.
Functional Programming Patterns
map(), filter(), reduce()
functools — Essential Utilities
Recursion — When and How
A recursive function calls itself. Every recursive solution has two parts: a base case that stops the recursion, and a recursive case that reduces the problem to a smaller version of itself.
Memoization with lru_cache
Without memoization, recursive Fibonacci has exponential time complexity because it recomputes the same subproblems millions of times. With memoization, each subproblem is solved exactly once.
Tower of Hanoi — Classic Recursive Problem
Project: Recursive Expression Calculator
This project implements a complete arithmetic expression calculator using recursive descent parsing — the same technique used in real compilers and interpreters. It handles operator precedence, parentheses, and unary negation.
The grammar:
Each grammar rule maps directly to one recursive function — that is what makes this technique so elegant.
Exercises
Exercise 1 — Default Argument Trap (Easy)
Task: Identify and fix the mutable default argument bug in each function.
Exercise 2 — LEGB Scope Explorer (Easy)
Task: Without running the code, predict what each print outputs. Then run it to verify.
Exercise 3 — Closure Factory (Medium)
Task: Create a make_validator factory that returns a validator function.
Exercise 4 — Decorators from Scratch (Medium)
Task: Implement two decorators: timer (measures execution time) and retry (retries on exception up to N times).
Exercise 5 — *args/**kwargs Mastery (Medium)
Task: Write a format_table function that accepts rows as positional arguments and formatting options as keyword arguments.
Exercise 6 — Functional Pipeline (Medium)
Task: Build a pipeline function that composes functions left-to-right into a single callable.
Exercise 7 — Recursive Flatten (Hard)
Task: Write flatten to recursively flatten a nested list of any depth.
Exercise 8 — Memoized Dynamic Programming (Hard)
Task: Implement three classic DP problems using lru_cache.
Excellent work. You now understand Python functions at a depth most engineers never reach — first-class functions, the mutable default trap, the LEGB rule, closures and cell objects, memoization, and recursive descent parsing. The next lesson covers Python's built-in data structures with the same rigor.