Naming logic
You’ve typed 2 ** (32 - prefix) - 2 at least a dozen times since Lesson 2.
A function lets you type it once, name it, and call the name forever:
def usable_hosts(prefix_len):
return 2 ** (32 - prefix_len) - 2
defopens the definition; the indented block is the bodyprefix_lenis a parameter — a placeholder filled in at call timereturnhands the result back to whoever called
>>> usable_hosts(24)
254
>>> usable_hosts(26) + usable_hosts(27) # results compose
92
That second line is the point. Returned values feed math, comparisons, f-strings, other functions — your logic becomes a building block.
return, not print
The single most common beginner detour: writing functions that print
their answer instead of returning it.
def show_hosts(prefix_len): # looks fine in the REPL...
print(2 ** (32 - prefix_len) - 2)
>>> x = show_hosts(24)
254 # printed on the way through
>>> print(x)
None # ...but nothing came BACK
Parameters that earn their keep
Defaults let a function assume the normal case while staying overridable:
def connect(host, port=22):
return f"{host}:{port}"
>>> connect("den-acc-sw01") # default applies
'den-acc-sw01:22'
>>> connect("den-acc-sw01", port=830) # NETCONF day? override it
'den-acc-sw01:830'
Naming arguments at the call site (port=830) costs nothing and makes the
line self-documenting — six months from now, connect("sw1", 830) makes
you check the definition; connect("sw1", port=830) doesn’t.
Multiple results, and no result
Returning a tuple gives a function two answers; unpacking catches them:
import re
def parse_interface(line):
m = re.search(r"^(\S+)\s+(\d+\.\d+\.\d+\.\d+)", line)
if m:
return m.group(1), m.group(2) # a tuple
return None # the "not found" contract
name, ip = parse_interface("Gi1/0/1 10.20.30.1 YES manual up up")
Returning None for “nothing found” is the same contract you’ve consumed
from .get() and re.search() since Lesson 4 — now you’re the one
honoring it, and callers guard with if result: exactly as you’ve learned.
Composition: the audit, rebuilt
Watch Lesson 3’s audit become three named, testable pieces:
def get_interface_names(config_lines):
"""Return the interface names in a config, in order."""
names = []
for line in config_lines:
if line.startswith("interface "):
names.append(line.split()[1])
return names
def missing_descriptions(config_lines):
"""Return interface names whose next line isn't a description."""
findings = []
for i, line in enumerate(config_lines):
if line.startswith("interface "):
has_next = i + 1 < len(config_lines)
if not has_next or not config_lines[i + 1].startswith(" description"):
findings.append(line.split()[1])
return findings
def format_report(hostname, findings):
"""Render findings as a human-readable block."""
if not findings:
return f"{hostname}: clean"
lines = [f"{hostname}: {len(findings)} findings"]
for name in findings:
lines.append(f" - {name} missing description")
return "\n".join(lines)
View diagram source — it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart LR
A["config_lines"] --> B["missing_descriptions()<br/>lines → names"]
B -- "findings list" --> C["format_report()<br/>findings → text"]
C -- "report string" --> D["print / write to file<br/>(the program's edge)"] Each piece has one job, a docstring stating its contract, and a return
value the next piece consumes. The triple-quoted docstring is the same
text help() shows and the same format every lab handed you — writing one
is how your future self learns to trust your past self.
Output appears here. First run downloads the Python runtime (~10 MB), so give it a few seconds.
Exercises (graded)
cd labs/python-foundations/lesson06
pytest -q
Five functions in exercises.py:
subnet_summary(prefix_len)— a formatted one-liner from the host mathnormalize_hostname(raw, domain="corp.example.com")— cleanup with a defaultparse_interface_line(line)— your Lesson 5 regex, now returning a tuple orNoneaudit_inventory(inventory, required_key)— hostnames missing a required factformat_findings(findings)— render(hostname, problem)tuples into a report
def report(): print("4 findings") — then x = report(). What is x?
Given def connect(host, port=22), which call opens NETCONF (port 830) to sw1?
parse_interface(line) returns ("Gi1/0/1", "10.20.30.1") on success and None on failure. What must callers do?
Summary
Functions name your logic and define its contract: parameters in,
return out — and return, not print, because returned values compose
while printed ones evaporate (leaving None behind). Defaults make the
common case free and the unusual case explicit, tuples carry multiple
results, None signals “not found” under the same guard discipline you
already practice, and docstrings write the contract down. Composition —
small functions feeding each other — is the actual architecture of
production automation, and it’s why every grader in this course tests
functions. Next lesson: classes, where the device dict and the functions
that operate on it finally move in together.