There is a common way of "writing" functions that is really assembling them: search for a function that looks close, copy it, rename the variables, and hope. When it breaks, copy a different one. Code produced this way can pass a demo and still be unexplainable by the person who shipped it, and unexplainable code cannot be debugged under production pressure.

Consider a function assembled exactly that way:

def process_temperature_data(readings, window=10, threshold=None): """Process temperature readings with moving average.""" if threshold is None: threshold = [] # Why is threshold a list? Nobody knows. if not hasattr(process_temperature_data, 'history'): process_temperature_data.history = [] # What does this do? process_temperature_data.history.extend(readings) # Why does this matter? result = [sum(readings[i:i+window])/window for i in range(len(readings)-window+1)] return result if result else readings

It "works" sometimes. This module builds the understanding needed to see exactly what is wrong with it: scope, return semantics, mutable defaults, and side effects.

What Functions Actually Are

1. Functions Are Not Just Named Code Blocks

# The naive view of functions: def calculate_stuff(): # Some code here x = 10 y = 20 print(x + y) # What it misses: Functions create a new scope! x = 100 calculate_stuff() # Prints 30, not 120 print(x) # Still 100, function didn't change global x

2. The Return vs Print Confusion

# A constant beginner mistake: def get_average(numbers): avg = sum(numbers) / len(numbers) print(avg) # WRONG! This just displays result = get_average([1, 2, 3]) print(result) # None! The function returned nothing # The correct version: def get_average(numbers): avg = sum(numbers) / len(numbers) return avg # Actually gives back the value result = get_average([1, 2, 3]) # Now result = 2.0

3. The Mutable Default Argument Trap

# A bug that can haunt production for weeks: def log_reading(value, history=[]): # DANGER! history.append(value) return history # First sensor sensor1_log = log_reading(100) # [100] sensor1_log = log_reading(102) # [100, 102] - Expected! # Second sensor - SURPRISE! sensor2_log = log_reading(200) # [100, 102, 200] - WHAT?! # Both sensors share the SAME list!

Functions in Industrial Systems

Pure Functions vs Side Effects

# PURE FUNCTION - Predictable, testable def celsius_to_fahrenheit(celsius): """Always returns same output for same input.""" return (celsius * 9/5) + 32 # SIDE EFFECTS - Harder to test, can cause bugs sensor_state = {'alerts': [], 'readings': []} def check_temperature(temp): """Modifies external state - side effect!""" if temp > 1500: sensor_state['alerts'].append(f"High temp: {temp}") # Side effect send_email_alert() # Another side effect sensor_state['readings'].append(temp) # And another return temp > 1500

Function Composition

# Small, focused functions def read_sensor(sensor_id): """Read single sensor value.""" return sensor_readings[sensor_id] def validate_reading(value, min_val=0, max_val=2000): """Check if reading is valid.""" return min_val <= value <= max_val def convert_to_celsius(fahrenheit): """Convert F to C.""" return (fahrenheit - 32) * 5/9 def process_sensor(sensor_id): """Compose smaller functions.""" raw_value = read_sensor(sensor_id) if validate_reading(raw_value): return convert_to_celsius(raw_value) return None
The Core Idea:
Functions are contracts. The signature promises what goes in, the return promises what comes out, and side effects are the fine print. Break the contract, or fail to read the fine print, and production breaks with it.

Exercise: Function Mastery

Build a Temperature Alert System

Each of these functions has a real bug. Fix all four, then combine them into a working alert system:

# Fix these broken functions: def calculate_average(readings, last_n): """Calculate average of last n readings.""" # BUG: What if readings has fewer than last_n elements? return sum(readings[-last_n:]) / last_n def add_alert(message, alerts=[]): """Add alert to list.""" # BUG: Mutable default argument! alerts.append(message) return alerts def check_sensor_health(readings): """Check if sensor is working properly.""" avg = sum(readings) / len(readings) print(f"Average: {avg}") # BUG: No return statement! def process_batch(sensor_data): """Process batch of sensor data.""" for reading in sensor_data: if reading > 1500: alert = True else: alert = False return alert # BUG: Only returns last value! # Your challenge: Fix all bugs and create a working system

The Function Mastery Checklist

On exit from this module you should be able to say:
✓ I can explain what parameters vs arguments are
✓ I understand scope and namespace
✓ I know when to use return vs print
✓ I can identify and fix side effects
✓ I understand *args and **kwargs
✓ I can write pure functions
✓ I know why mutable defaults are dangerous