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
- Create a calculator with separate functions for each operation
- Write a function that validates usernames (length, characters)
- Build a temperature converter with Celsius, Fahrenheit, Kelvin
- 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