DEV Community

Thiruvengadam Sakthivel
Thiruvengadam Sakthivel

Posted on

🛠️ Day 3 – Python Functions & Functional Power

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

Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

2. *args and kwargs (Dynamic Arity) 📦

Sometimes you don't know exactly how many arguments a user will pass to your function.

  • *args collects extra positional arguments into a Tuple.
  • kwargs collects 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'}

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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:

  1. Local: Inside the current function.
  2. Enclosing: Inside any enclosing (outer) functions (relevant to nested functions/closures).
  3. Global: At the top level of the module/file.
  4. 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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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.")

Enter fullscreen mode Exit fullscreen mode

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}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)