GadaaLabs
Python Mastery — From Zero to AI Engineering
Lesson 2

Functions, Scope, Closures & Functional Programming

32 min

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

*args and **kwargs

*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dict.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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 /):

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Functional Programming Patterns

map(), filter(), reduce()

Python
Click Run to execute — Python runs in your browser via WebAssembly

functools — Essential Utilities

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Tower of Hanoi — Classic Recursive Problem

Python
Click Run to execute — Python runs in your browser via WebAssembly

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:

expression := term (('+' | '-') term)*
term       := factor (('*' | '/') factor)*
factor     := unary ('^' unary)*
unary      := '-' unary | primary
primary    := number | '(' expression ')'

Each grammar rule maps directly to one recursive function — that is what makes this technique so elegant.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercises

Exercise 1 — Default Argument Trap (Easy)

Task: Identify and fix the mutable default argument bug in each function.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 2 — LEGB Scope Explorer (Easy)

Task: Without running the code, predict what each print outputs. Then run it to verify.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 3 — Closure Factory (Medium)

Task: Create a make_validator factory that returns a validator function.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 4 — Decorators from Scratch (Medium)

Task: Implement two decorators: timer (measures execution time) and retry (retries on exception up to N times).

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 5 — *args/**kwargs Mastery (Medium)

Task: Write a format_table function that accepts rows as positional arguments and formatting options as keyword arguments.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 6 — Functional Pipeline (Medium)

Task: Build a pipeline function that composes functions left-to-right into a single callable.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 7 — Recursive Flatten (Hard)

Task: Write flatten to recursively flatten a nested list of any depth.

Python
Click Run to execute — Python runs in your browser via WebAssembly

Exercise 8 — Memoized Dynamic Programming (Hard)

Task: Implement three classic DP problems using lru_cache.

Python
Click Run to execute — Python runs in your browser via WebAssembly

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.