🐍 Week 1: Python Foundations

Day 4: Functions

Duration: 5 Hours

📚 Learning Objectives

By the end of this session, you will be able to:

  • Define and call functions using def
  • Use parameters and arguments effectively
  • Return values from functions
  • Understand variable scope (local vs global)
  • Write documentation strings (docstrings)

📖 Core Concepts (2 Hours)

What are Functions?

Functions are reusable blocks of code that perform specific tasks. They help organize code and avoid repetition.

# Why functions? # - Reusability: Write once, use many times # - Organization: Break complex problems into parts # - Readability: Give meaningful names to operations # - Testing: Easy to test individual pieces # Without functions (repetitive) print("=" * 30) print("Starting backup...") print("=" * 30) # ... later in code ... print("=" * 30) print("Starting deployment...") print("=" * 30) # With functions (clean!) def show_header(message): print("=" * 30) print(message) print("=" * 30) show_header("Starting backup...") show_header("Starting deployment...")

Defining Functions

# Basic function definition def greet(): print("Hello, World!") # Call the function greet() # Output: Hello, World! # Function with parameter def greet_user(name): print(f"Hello, {name}!") greet_user("Alice") # Hello, Alice! greet_user("Bob") # Hello, Bob! # Multiple parameters def greet_full(first, last): print(f"Hello, {first} {last}!") greet_full("John", "Doe") # Hello, John Doe!

Parameters and Arguments

# Parameters = variables in function definition # Arguments = values passed when calling # Positional arguments (order matters) def create_user(name, role, department): print(f"Creating {name} as {role} in {department}") create_user("Alice", "Admin", "IT") # Keyword arguments (order doesn't matter) create_user(role="Developer", department="Engineering", name="Bob") # Default parameter values def connect(host, port=22, protocol="ssh"): print(f"Connecting to {host}:{port} via {protocol}") connect("server1") # Uses defaults connect("server2", 8080) # Custom port connect("server3", 443, "https") # All custom # Mixing positional and keyword connect("server4", protocol="sftp") # Works!

Return Values

# Functions can return values # Single return value def add(a, b): return a + b result = add(5, 3) print(result) # 8 # Return in conditional def get_status(code): if code == 200: return "OK" elif code == 404: return "Not Found" elif code == 500: return "Server Error" else: return "Unknown" status = get_status(200) print(status) # OK # Multiple return values (tuple) def get_server_info(): return "web-01", "192.168.1.10", 8080 name, ip, port = get_server_info() print(f"{name}: {ip}:{port}") # Return None (implicit) def say_hello(): print("Hello!") # No return statement = returns None result = say_hello() print(result) # None

Variable Scope

# Scope = where variables are accessible # Local scope (inside function) def my_function(): local_var = "I'm local" print(local_var) # Works my_function() # print(local_var) # Error! Not accessible outside # Global scope (outside function) global_var = "I'm global" def read_global(): print(global_var) # Can read global read_global() # Works! # Modifying global (use global keyword) counter = 0 def increment(): global counter counter += 1 increment() print(counter) # 1 # Best practice: Avoid global, use return instead def increment_value(value): return value + 1 counter = 0 counter = increment_value(counter) print(counter) # 1

Docstrings

# Docstrings document what functions do def calculate_disk_usage(used, total): """ Calculate disk usage percentage. Args: used: Used disk space in GB total: Total disk space in GB Returns: float: Percentage of disk used Example: >>> calculate_disk_usage(80, 100) 80.0 """ return (used / total) * 100 # Access docstring print(calculate_disk_usage.__doc__) help(calculate_disk_usage) # Single line docstring def is_healthy(cpu, memory): """Return True if server is healthy (both metrics under 80%).""" return cpu < 80 and memory < 80

*args and **kwargs

# *args = variable number of positional arguments def sum_all(*numbers): """Sum any number of arguments.""" total = 0 for num in numbers: total += num return total print(sum_all(1, 2)) # 3 print(sum_all(1, 2, 3, 4, 5)) # 15 # **kwargs = variable number of keyword arguments def create_config(**settings): """Create config from keyword arguments.""" print("Configuration:") for key, value in settings.items(): print(f" {key}: {value}") create_config(host="localhost", port=8080, debug=True) # Combining both def flexible_function(required, *args, **kwargs): print(f"Required: {required}") print(f"Args: {args}") print(f"Kwargs: {kwargs}") flexible_function("first", 1, 2, 3, name="test", active=True)

Lambda Functions

# Lambda = small anonymous functions # Regular function def square(x): return x ** 2 # Lambda equivalent square = lambda x: x ** 2 print(square(5)) # 25 # Common use: sorting servers = [ {"name": "web-01", "cpu": 45}, {"name": "web-02", "cpu": 85}, {"name": "web-03", "cpu": 30}, ] # Sort by CPU usage servers_sorted = sorted(servers, key=lambda s: s["cpu"]) for server in servers_sorted: print(f"{server['name']}: {server['cpu']}%") # With map() and filter() numbers = [1, 2, 3, 4, 5] squared = list(map(lambda x: x**2, numbers)) print(squared) # [1, 4, 9, 16, 25] evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # [2, 4]

🔬 Hands-on Lab (2.5 Hours)

Lab 1: Basic Functions

  • Create greeting functions
  • Use parameters and return values
  • Practice function composition
# Lab 1: basic_functions.py # Greeting function def greet(name, greeting="Hello"): """Return a greeting message.""" return f"{greeting}, {name}!" # Test the function print(greet("Alice")) print(greet("Bob", "Hi")) print(greet("Charlie", greeting="Welcome")) # Math functions def add(a, b): """Add two numbers.""" return a + b def multiply(a, b): """Multiply two numbers.""" return a * b def power(base, exp=2): """Raise base to exp power (default: square).""" return base ** exp # Function composition result = add(multiply(2, 3), power(4)) print(f"Result: {result}") # (2*3) + (4^2) = 6 + 16 = 22

Lab 2: Server Monitoring Functions

  • Create health check functions
  • Use multiple return values
  • Build a monitoring system
# Lab 2: server_functions.py def check_cpu(usage): """ Check if CPU usage is acceptable. Args: usage: CPU usage percentage Returns: tuple: (status, message) """ if usage > 90: return "CRITICAL", f"CPU at {usage}% - Immediate action needed!" elif usage > 80: return "WARNING", f"CPU at {usage}% - Monitor closely" else: return "OK", f"CPU at {usage}% - Normal" def check_memory(usage): """Check memory usage status.""" if usage > 90: return "CRITICAL", f"Memory at {usage}%" elif usage > 80: return "WARNING", f"Memory at {usage}%" return "OK", f"Memory at {usage}%" def check_disk(usage): """Check disk usage status.""" if usage > 90: return "CRITICAL", f"Disk at {usage}%" elif usage > 80: return "WARNING", f"Disk at {usage}%" return "OK", f"Disk at {usage}%" def get_overall_health(cpu, memory, disk): """Get overall server health status.""" cpu_status, cpu_msg = check_cpu(cpu) mem_status, mem_msg = check_memory(memory) disk_status, disk_msg = check_disk(disk) # Determine overall status statuses = [cpu_status, mem_status, disk_status] if "CRITICAL" in statuses: overall = "CRITICAL" elif "WARNING" in statuses: overall = "WARNING" else: overall = "HEALTHY" return overall, [cpu_msg, mem_msg, disk_msg] # Test the monitoring system print("=== Server Health Check ===\n") servers = [ {"name": "web-01", "cpu": 45, "memory": 60, "disk": 70}, {"name": "web-02", "cpu": 85, "memory": 75, "disk": 92}, {"name": "db-01", "cpu": 30, "memory": 95, "disk": 55}, ] for server in servers: status, messages = get_overall_health( server["cpu"], server["memory"], server["disk"] ) print(f"Server: {server['name']}") print(f"Status: {status}") for msg in messages: print(f" - {msg}") print()

Lab 3: Configuration Builder

  • Use **kwargs for flexible config
  • Implement default values
  • Return formatted configuration
# Lab 3: config_builder.py def build_connection_string(host, port, **options): """ Build a database connection string. Args: host: Database host port: Database port **options: Additional options (user, password, database, etc.) Returns: str: Formatted connection string """ conn = f"{host}:{port}" if "database" in options: conn += f"/{options['database']}" params = [] for key, value in options.items(): if key != "database": params.append(f"{key}={value}") if params: conn += "?" + "&".join(params) return conn def build_server_config(name, **settings): """Build server configuration with defaults.""" # Default values config = { "name": name, "port": 8080, "protocol": "http", "max_connections": 100, "timeout": 30, "ssl": False, } # Override with provided settings config.update(settings) return config def print_config(config): """Print configuration in a nice format.""" print("Configuration:") print("-" * 30) for key, value in config.items(): print(f" {key}: {value}") print() # Test connection strings print("=== Connection Strings ===\n") conn1 = build_connection_string("localhost", 5432, database="myapp") print(f"Simple: {conn1}") conn2 = build_connection_string( "db.example.com", 5432, database="production", user="admin", ssl="true", timeout="10" ) print(f"Full: {conn2}") # Test server configs print("\n=== Server Configurations ===\n") # Default config config1 = build_server_config("web-server") print_config(config1) # Custom config config2 = build_server_config( "api-server", port=443, protocol="https", ssl=True, max_connections=500 ) print_config(config2)

Lab 4: Utility Functions Library

  • Create a reusable utilities module
  • Write proper docstrings
  • Test all functions
# Lab 4: utils.py def format_bytes(bytes_value): """ Convert bytes to human readable format. Args: bytes_value: Size in bytes Returns: str: Formatted string (e.g., "1.5 GB") """ for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if bytes_value < 1024: return f"{bytes_value:.1f} {unit}" bytes_value /= 1024 return f"{bytes_value:.1f} PB" def format_uptime(seconds): """Convert seconds to human readable uptime.""" days = seconds // 86400 hours = (seconds % 86400) // 3600 minutes = (seconds % 3600) // 60 parts = [] if days > 0: parts.append(f"{days}d") if hours > 0: parts.append(f"{hours}h") if minutes > 0: parts.append(f"{minutes}m") return " ".join(parts) if parts else "0m" def is_valid_ip(ip): """Check if string is a valid IPv4 address.""" parts = ip.split(".") if len(parts) != 4: return False for part in parts: if not part.isdigit(): return False num = int(part) if num < 0 or num > 255: return False return True def parse_log_line(line): """ Parse a simple log line. Expected format: [LEVEL] MESSAGE Returns: dict: {"level": str, "message": str} """ if line.startswith("[") and "]" in line: level_end = line.index("]") level = line[1:level_end] message = line[level_end + 2:] return {"level": level, "message": message} return {"level": "UNKNOWN", "message": line} # Test all functions if __name__ == "__main__": print("=== Testing Utility Functions ===\n") # Test format_bytes print("format_bytes():") print(f" 1024 bytes = {format_bytes(1024)}") print(f" 1.5 GB = {format_bytes(1024**3 * 1.5)}") # Test format_uptime print("\nformat_uptime():") print(f" 3661 seconds = {format_uptime(3661)}") print(f" 90061 seconds = {format_uptime(90061)}") # Test is_valid_ip print("\nis_valid_ip():") print(f" 192.168.1.1: {is_valid_ip('192.168.1.1')}") print(f" 256.1.1.1: {is_valid_ip('256.1.1.1')}") print(f" invalid: {is_valid_ip('invalid')}") # Test parse_log_line print("\nparse_log_line():") log1 = "[INFO] Server started" log2 = "[ERROR] Connection failed" print(f" {log1} -> {parse_log_line(log1)}") print(f" {log2} -> {parse_log_line(log2)}")

📝 Practice Exercises

  1. Create a calculator with separate functions for each operation
  2. Write a function that validates usernames (length, characters)
  3. Build a temperature converter with Celsius, Fahrenheit, Kelvin
  4. Create a password strength checker function

💡 DevOps Relevance

Why this matters:

  • Functions: Organize automation scripts into reusable parts
  • Parameters: Make scripts configurable and flexible
  • Return values: Check operation success/failure
  • Docstrings: Document automation for team members

✅ Day 4 Checklist

  • Can define and call functions
  • Understand parameters vs arguments
  • Can use default parameter values
  • Know how to return values
  • Understand local vs global scope
  • Can write proper docstrings
  • Know about *args and **kwargs