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:
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!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()) # 24. 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.
