Welcome to Day 3! Today, we transition from writing basic scripts to building reusable, modular, and highly flexible building blocks. We will look at how Python handles arguments, the strict rules of variable scope, and how functions can be treated as first-class citizens to create highly dynamic utilities. 🚀
1. Parameters & Arguments (Default & Keyword-Only) ⚙️
In Python, you can define parameters that make your functions incredibly flexible.
- Default Arguments: Allow you to set fallback values for parameters if the caller doesn't provide them.
-
Keyword-Only Arguments: Enforce that certain arguments must be passed by name rather than position. You declare these by placing an asterisk
*in your parameter list; everything after the*must be named.
🌱 Easy Starter Example
# 'msg' has a default value. 'shout' is a keyword-only argument.
def greet(name, msg="Hello", *, shout=False):
greeting = f"{msg}, {name}!"
return greeting.upper() if shout else greeting
# Usage
print(greet("Alice")) # Output: Hello, Alice!
print(greet("Bob", "Welcome")) # Output: Welcome, Bob!
# print(greet("Charlie", shout=True)) # Output: HELLO, CHARLIE!
# greet("Dave", "Hi", True) # ❌ TypeError: keyword-only argument required
Real-World Application Example: API Request Configurer 🔌
# Enforcing safety defaults for API timeouts and token visibility
def send_request(url, payload, *, timeout=10, retries=3):
print(f"Sending data to {url} (Timeout: {timeout}s, Retries: {retries})")
# Request logic would go here...
# Caller must explicitly state overrides, preventing accidental index errors
send_request("https://api.example.com", {"data": 1}, timeout=5, retries=1)
2. *args and kwargs (Dynamic Arity) 📦
Sometimes you don't know exactly how many arguments a user will pass to your function.
-
*argscollects extra positional arguments into a Tuple. -
kwargscollects extra keyword arguments into a Dictionary.
🌱 Easy Starter Example
def dynamic_demo(*args, **kwargs):
print("args:", args) # Tuple of positional args
print("kwargs:", kwargs) # Dict of keyword args
dynamic_demo(1, 2, 3, user="Dev", role="Admin")
# Output:
# args: (1, 2, 3)
# kwargs: {'user': 'Dev', 'role': 'Admin'}
Real-World Application Example: HTML Element Generator 🎨
def build_html_tag(tag_name, *content, **attributes):
# Format the attributes dict into a string of key="value"
attr_str = "".join([f' {key}="{val}"' for key, val in attributes.items()])
# Join all separate content items together
inner_text = " ".join(content)
return f"<{tag_name}{attr_str}>{inner_text}</{tag_name}>"
# Create a customized HTML link
link = build_html_tag("a", "Click", "Here", "To", "Learn", href="https://python.org", target="_blank")
print(link)
# Output: <a href="https://python.org" target="_blank">Click Here To Learn</a>
3. Scope & The LEGB Rule 🌐
Variable visibility in Python is governed by the LEGB Rule. When you look up a variable name, Python checks four scopes in a strict order:
- Local: Inside the current function.
- Enclosing: Inside any enclosing (outer) functions (relevant to nested functions/closures).
- Global: At the top level of the module/file.
-
Built-in: Python's pre-loaded keywords (like
len,sum,print).
🌱 Easy Starter Example
x = "Global"
def outer_function():
x = "Enclosing"
def inner_function():
x = "Local"
print(f"Inside inner: {x}") # Finds Local first
inner_function()
print(f"Inside outer: {x}") # Finds Enclosing first
outer_function()
print(f"Global scope: {x}") # Finds Global first
Real-World Application Example: Modifying Enclosing Scope 🔧
# To modify variables in Global or Enclosing scopes, use 'global' or 'nonlocal'
def create_click_counter():
count = 0 # Enclosing scope variable
def click():
nonlocal count # Declares we want to edit 'count' from the outer function
count += 1
return count
return click
counter = create_click_counter()
print(counter()) # Output: 1
print(counter()) # Output: 2
4. First-Class Functions & Closures 🤝
In Python, functions are First-Class Citizens. This means you can assign them to variables, pass them as arguments to other functions, and even return them from other functions.
A Closure is an inner function that "remembers" and has access to variables from its enclosing outer scope, even after the outer function has finished executing.
🌱 Easy Starter Example
def make_multiplier(factor):
# This is a closure! It remembers 'factor'
def multiply(number):
return number * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10)) # Output: 20
print(triple(10)) # Output: 30
Real-World Application Example: Custom Logger Creator 📝
import datetime
# Create customized loggers with specific prefix headers
def make_logger(prefix: str):
def log(message: str) -> None:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] [{prefix.upper()}]: {message}")
return log
# Instantiate tailored logging functions
info_log = make_logger("info")
error_log = make_logger("error")
# Usage
info_log("Server started successfully.")
error_log("Database connection timeout.")
5. Practice Challenge: Building Reusable Utility Functions 🏋️
Let's tie all these rules together to build a robust Data Processing Utility Engine.
We want to build a reusable utility function that processes data tables (lists of dictionaries) by applying custom transformations, filtering rules, and applying fallback configuration defaults.
# 1. Custom transformation functions (First-Class Functions)
def tax_calculator(amount):
return amount * 1.15
def uppercase_formatter(text):
return str(text).upper()
# 2. Main data processor utility utilizing *args, **kwargs, and keyword defaults
def process_transactions(transactions: list[dict], *fields_to_transform, transformer_func, **default_fallbacks) -> list[dict]:
"""
Processes transaction logs by applying a 'transformer_func' to specified fields.
Fills in any missing fields using key-value 'default_fallbacks'.
"""
processed_data = []
for item in transactions:
# Create a shallow copy to keep the original list pure
temp_item = item.copy()
# Apply fallbacks for missing data
for key, val in default_fallbacks.items():
if key not in temp_item:
temp_item[key] = val
# Apply transformation to specified fields (passed via *args)
for field in fields_to_transform:
if field in temp_item:
temp_item[field] = transformer_func(temp_item[field])
processed_data.append(temp_item)
return processed_data
# --- RUNNING THE UTILITY ---
raw_invoices = [
{"invoice_id": "inv_001", "subtotal": 100},
{"invoice_id": "inv_002", "subtotal": 250, "currency": "USD"},
{"invoice_id": "inv_003"}, # Missing invoice_id and subtotal fields
]
# Run processor to apply tax calculation on subtotal and default missing currency to 'EUR'
completed_invoices = process_transactions(
raw_invoices,
"subtotal", # *args: Fields to modify
transformer_func=tax_calculator, # Keyword-only transformer
currency="EUR", # **kwargs: Fallbacks
subtotal=0.0 # **kwargs: Fallbacks
)
for invoice in completed_invoices:
print(invoice)
# Output:
# {'invoice_id': 'inv_001', 'subtotal': 114.99999999999999, 'currency': 'EUR'}
# {'invoice_id': 'inv_002', 'subtotal': 287.5, 'currency': 'USD'}
# {'invoice_id': 'inv_003', 'currency': 'EUR', 'subtotal': 0.0}
Top comments (0)