Drill 04: Functions & Classes

Functions with args/kwargs, lambda, classes, and modern dataclasses.

Run This Drill

bash ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/examples/python-drills/04-functions.sh

Drill Script

#!/bin/bash
# PYTHON DRILL 04: FUNCTIONS & CLASSES
# Paste this entire script into your terminal
# Topics: def, args, kwargs, classes, dataclasses

echo "=================================================================="
echo "             PYTHON DRILL 04: FUNCTIONS & CLASSES                "
echo "=================================================================="
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.1: BASIC FUNCTIONS"
echo "Definition, arguments, return values"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
# Simple function
def greet(name):
    return f"Hello, {name}!"

print(greet("admin"))

# Multiple arguments
def connect(host, port, timeout=30):
    return f"Connecting to {host}:{port} (timeout: {timeout}s)"

print(connect("10.50.1.20", 443))
print(connect("10.50.1.20", 443, timeout=60))

# Return multiple values
def parse_endpoint(endpoint):
    host, port = endpoint.split(":")
    return host, int(port)

h, p = parse_endpoint("10.50.1.20:443")
print(f"Host: {h}, Port: {p}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.2: *args AND **kwargs"
echo "Variable arguments"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
# *args - variable positional arguments
def sum_all(*numbers):
    return sum(numbers)

print(f"sum_all(1,2,3): {sum_all(1, 2, 3)}")
print(f"sum_all(10,20,30,40): {sum_all(10, 20, 30, 40)}")

# **kwargs - variable keyword arguments
def create_server(**config):
    for key, value in config.items():
        print(f"  {key}: {value}")

print("create_server(hostname='ise-01', ip='10.50.1.20'):")
create_server(hostname="ise-01", ip="10.50.1.20", port=443)

# Combining both
def api_call(endpoint, *args, **kwargs):
    print(f"Endpoint: {endpoint}")
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

print("\napi_call('/users', 1, 2, format='json'):")
api_call("/users", 1, 2, format="json", timeout=30)
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.3: LAMBDA FUNCTIONS"
echo "Anonymous functions for quick operations"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
# Basic lambda
square = lambda x: x ** 2
print(f"square(5): {square(5)}")

# Sorting with lambda
servers = [
    {"name": "ise-01", "cpu": 45},
    {"name": "bind-01", "cpu": 5},
    {"name": "vault-01", "cpu": 78}
]

# Sort by CPU
by_cpu = sorted(servers, key=lambda s: s["cpu"])
print("\nSorted by CPU:")
for s in by_cpu:
    print(f"  {s['name']}: {s['cpu']}%")

# Sort descending
by_cpu_desc = sorted(servers, key=lambda s: s["cpu"], reverse=True)
print("\nSorted by CPU (desc):")
for s in by_cpu_desc:
    print(f"  {s['name']}: {s['cpu']}%")

# Filter with lambda
high_cpu = list(filter(lambda s: s["cpu"] > 20, servers))
print(f"\nHigh CPU (>20%): {[s['name'] for s in high_cpu]}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.4: BASIC CLASSES"
echo "Object-oriented programming"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
class Server:
    def __init__(self, hostname, ip, port=22):
        self.hostname = hostname
        self.ip = ip
        self.port = port
        self.status = "unknown"

    def connect(self):
        return f"Connecting to {self.hostname} ({self.ip}:{self.port})"

    def set_status(self, status):
        self.status = status

    def __str__(self):
        return f"Server({self.hostname}, {self.ip}, {self.status})"

# Create instances
ise = Server("ise-01", "10.50.1.20", 443)
vault = Server("vault-01", "10.50.1.132", 8200)

print(ise.connect())
ise.set_status("active")
print(ise)

print(f"\nvault.hostname: {vault.hostname}")
print(f"vault.ip: {vault.ip}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.5: DATACLASSES"
echo "Modern Python for data containers"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
from dataclasses import dataclass, field
from typing import List

@dataclass
class Endpoint:
    hostname: str
    ip: str
    port: int = 22
    tags: List[str] = field(default_factory=list)

# Auto-generated __init__, __repr__, __eq__
ise = Endpoint("ise-01", "10.50.1.20", 443, ["production", "pan"])
print(f"Created: {ise}")
print(f"Hostname: {ise.hostname}")
print(f"Tags: {ise.tags}")

# Equality comparison works
ise2 = Endpoint("ise-01", "10.50.1.20", 443, ["production", "pan"])
print(f"\nise == ise2: {ise == ise2}")

# Create list of endpoints
endpoints = [
    Endpoint("ise-01", "10.50.1.20", 443),
    Endpoint("bind-01", "10.50.1.90", 53),
    Endpoint("vault-01", "10.50.1.132", 8200)
]

print("\n=== All endpoints ===")
for ep in endpoints:
    print(f"  {ep.hostname}: {ep.ip}:{ep.port}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.6: INHERITANCE"
echo "Extending classes"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
class Device:
    def __init__(self, hostname, ip):
        self.hostname = hostname
        self.ip = ip

    def ping(self):
        return f"Pinging {self.ip}..."

class NetworkDevice(Device):
    def __init__(self, hostname, ip, vendor):
        super().__init__(hostname, ip)
        self.vendor = vendor

    def show_version(self):
        return f"{self.vendor} device at {self.ip}"

class ISENode(NetworkDevice):
    def __init__(self, hostname, ip, roles):
        super().__init__(hostname, ip, "Cisco")
        self.roles = roles

    def get_roles(self):
        return f"{self.hostname} roles: {', '.join(self.roles)}"

# Use inheritance chain
ise = ISENode("ise-01", "10.50.1.20", ["pan", "mnt", "psn"])
print(ise.ping())  # From Device
print(ise.show_version())  # From NetworkDevice
print(ise.get_roles())  # From ISENode
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "YOUR TURN - TRY THESE:"
echo "------------------------------------------------------------------"
echo ""
echo "1. Create a function with default args:"
echo "   def fetch_data(url, timeout=30, retries=3):"
echo "       return f'Fetching {url} (timeout={timeout}, retries={retries})'"
echo ""
echo "2. Sort list of dicts:"
echo "   data = [{'name': 'z'}, {'name': 'a'}, {'name': 'm'}]"
echo "   sorted(data, key=lambda x: x['name'])"
echo ""
echo "3. Create a dataclass:"
echo "   from dataclasses import dataclass"
echo "   @dataclass"
echo "   class VLAN:"
echo "       id: int"
echo "       name: str"
echo "       subnet: str = ''"
echo ""
echo "------------------------------------------------------------------"
echo "KEY TAKEAWAYS:"
echo "1. def name(args, default=value): for functions"
echo "2. *args for variable positional, **kwargs for keyword"
echo "3. lambda x: expression for quick functions"
echo "4. class Name: with __init__ for classes"
echo "5. @dataclass for data containers"
echo "------------------------------------------------------------------"