Chapter 6 of 21
Control Flow & Conditionals
Current Study Module
Functions & Lambdas
Lists & Tuples
Chapter 05 • Functional Architecture

Functions, Scopes, Closures & Lambdas

In Python, functions are first-class citizens—they can be bound to identifiers, stored in hash tables, passed into higher-order functions, and returned from factory closures. Master parameter binding syntax (/ and *), the LEGB scoping rule, and how to avoid the infamous mutable default argument trap.

1. Modern Parameter Binding: Positional-Only (/) & Keyword-Only (*)

Python provides fine-grained control over how callers invoke your APIs using / (PEP 570) and *:

  • Parameters before / are Positional-Only (cannot be called with keyword syntax).
  • Parameters after * are Keyword-Only (must be called explicitly by name).
# Function enforcing strict caller contracts
def configure_cache(host, port, /, max_memory="2GB", *, enable_tls=True, timeout=30):
    """
    host, port: POSITIONAL-ONLY (e.g. configure_cache("127.0.0.1", 6379))
    max_memory: POSITIONAL OR KEYWORD
    enable_tls, timeout: KEYWORD-ONLY (must write enable_tls=False)
    """
    return f"Cache configured on ${host}:${port} with TLS=${enable_tls}"

# VALID:
print(configure_cache("redis.internal", 6379, enable_tls=True))

# INVALID (Raises TypeError):
# configure_cache(host="redis.internal", port=6379) -> host is positional-only!

2. The Mutable Default Argument Bug (And How to Fix It)

In Python, default parameter expressions are evaluated once at function definition time, not each time the function is called! If you use a mutable object (like a list or dict) as a default parameter, that single object is shared across all invocations:

❌ THE DANGEROUS ANTI-PATTERN:
def append_tag(tag, tag_list=[]):
    tag_list.append(tag)
    return tag_list

print(append_tag("python"))  # ['python']
print(append_tag("ai"))      # ['python', 'ai'] !! Shared state!
✅ THE IDIOMATIC FIX (None Sentinel):
def append_tag(tag, tag_list=None):
    if tag_list is None:
        tag_list = []  # Allocates fresh list on every call!
    tag_list.append(tag)
    return tag_list

print(append_tag("python"))  # ['python']
print(append_tag("ai"))      # ['ai'] Clean & isolated!

3. The LEGB Variable Resolution Rule

When referencing a variable name, Python searches four concentric scope namespaces in strict order:

  • L (Local): Names assigned inside the executing function.
  • E (Enclosing): Names in the local scope of any enclosing/outer functions (closures).
  • G (Global): Names assigned at the top-level of the current module file.
  • B (Built-in): Pre-assigned standard names (len, range, Exception).

Use the nonlocal keyword to rebind variables in enclosing scopes, and global to modify module-level variables:

# Closure with nonlocal state counter
def create_request_counter():
    count = 0  # Enclosing scope variable
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter = create_request_counter()
print(counter())  # 1
print(counter())  # 2

4. Lambda Expressions & Higher-Order Functions

Lambdas are small, anonymous single-expression functions ideal for sorting keys, mappings, and filtering:

# Sorting complex records with lambda key
inventory = [
    {"sku": "A1", "price": 49.99, "stock": 12},
    {"sku": "B2", "price": 12.50, "stock": 85},
    {"sku": "C3", "price": 89.00, "stock": 4}
]

# Sort by price ascending
sorted_by_price = sorted(inventory, key=lambda item: item["price"])
print("Cheapest Item:", sorted_by_price[0]["sku"])

# Functional mapping & filtering
expensive_skus = list(map(lambda x: x["sku"], filter(lambda x: x["price"] > 20, inventory)))
print("Expensive SKUs:", expensive_skus)

Explore Related Tools

Hand-picked utilities and calculators related to this tool.

Related Tools