Conditionals: code that judges
Everything in automation reduces to questions with yes/no answers. Is this
line an interface? Is this VLAN in the voice range? Does this config contain
aaa new-model? In Python, a question is an expression that evaluates to a
boolean - True or False:
>>> vlan = 150
>>> vlan >= 100
True
>>> vlan == 150 # == asks; = assigns
True
>>> "aaa new-model" in config
False
if acts on the answer, elif chains more questions, else catches
everything left:
if vlan < 100:
role = "users"
elif vlan < 200:
role = "voice"
elif vlan < 300:
role = "management"
else:
role = "other"
The indented block under each branch is what runs - indentation isn’t
style in Python, it is the syntax. Four spaces, consistently, never tabs.
Combine conditions with and, or, and not:
if line.startswith("interface") and "Te" in line:
print("found an uplink stanza")
One more idea that makes code read like English - truthiness. Empty
things ("", [], 0) count as False; non-empty things count as True:
if matches: # "if we found anything"
print(f"{len(matches)} findings")
else:
print("clean")
Loops: code that scales
A for loop runs a block once per item in a list. That’s the whole
mechanism - and it’s the difference between checking one switch and checking
the district:
devices = ["acc-sw01", "acc-sw02", "core-sw01"]
for device in devices:
print(f"auditing {device}...")
You choose the loop variable’s name. Choose well - for line in lines,
for intf in interfaces, for device in devices read like sentences;
for x in y reads like a puzzle.
The accumulator pattern - most of parsing is this
Start with an empty list, loop, keep what matches:
interfaces = []
for line in config_lines:
if line.startswith("interface "):
interfaces.append(line)
Four lines, and you’ve filtered a 400-line config down to its interface stanzas. Lesson 5 adds regex for fancier matching and Lesson 6 wraps this in reusable functions, but the skeleton never changes: empty list → loop → condition → append.
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 TD
A["interfaces = []"] --> B{"more lines<br/>in the config?"}
B -- "yes" --> C{"line starts with<br/>'interface '?"}
C -- "yes" --> D["interfaces.append(line)"]
C -- "no" --> B
D --> B
B -- "no" --> E["return interfaces"]enumerate(): findings need line numbers
An audit finding without a line number is a scavenger hunt.
enumerate(lines) yields (index, item) pairs:
for i, line in enumerate(config_lines):
if "switchport mode trunk" in line:
print(f"line {i + 1}: {line.strip()}")
(The i + 1 is because humans count config lines from 1, not 0.)
break and continue - loop steering
break exits the loop entirely (you found what you came for);
continue skips to the next item (this one doesn’t apply):
for line in config_lines:
if line.startswith("!"):
continue # comment separator - skip it
if line == "end":
break # nothing meaningful after this
process(line)
Output appears here. First run downloads the Python runtime (~10 MB), so give it a few seconds.
Prove it (graded lab)
Write each function in the editor, then Run grader - it runs the lesson’s
real checks right here in your browser, nothing to install. Green means the
skill is yours, and these are the building blocks the Lesson 10 capstone
(netaudit) is assembled from.
Five functions - number five is the first real audit:
classify_vlan(vlan_id)- map a VLAN ID to its role by rangefind_interfaces(config_lines)- all lines that open an interface stanzacount_matching(lines, text)- how many lines containtextaccess_ports(interfaces)- everything that isn’t a TenGig uplinkmissing_descriptions(config_lines)- challenge: interface names whose next line is not a description (a real compliance check - combine enumerate, indexing, and.startswith())
Reveal a reference solution
"""Lesson 3 reference solutions. Read after an honest attempt."""
def classify_vlan(vlan_id):
if 1 <= vlan_id <= 99:
return "users"
elif 100 <= vlan_id <= 199:
return "voice"
elif 200 <= vlan_id <= 299:
return "management"
else:
return "other"
def find_interfaces(config_lines):
found = []
for line in config_lines:
if line.startswith("interface "):
found.append(line)
return found
def count_matching(lines, text):
count = 0
for line in lines:
if text in line:
count = count + 1
return count
def access_ports(interfaces):
ports = []
for name in interfaces:
if not name.startswith("Te"):
ports.append(name)
return ports
def missing_descriptions(config_lines):
findings = []
for i, line in enumerate(config_lines):
if line.startswith("interface "):
name = line.split()[1]
has_next = i + 1 < len(config_lines)
if not has_next or not config_lines[i + 1].startswith(" description"):
findings.append(name)
return findings
Read it, close it, then re-type your own version from memory. Wrong turns are where the learning is.
Your script dies with SyntaxError pointing at: if hostname = "core-sw01": - what is wrong?
After: found = [] / for v in [10, 150, 250, 20]: / if v < 100: found.append(v) - what is found?
What does "if matches:" test, given matches is a list your loop built?
Summary
Conditionals turn facts into decisions: if/elif/else branch on comparisons
(==, not =), joined by and/or/not, with truthiness letting
if matches: read as plain English. Loops apply those decisions at scale:
for line in lines visits everything, the accumulator pattern (empty list →
loop → condition → append) filters configs in four lines, enumerate()
attaches the line numbers an audit needs, and break/continue steer.
You also learned the silent-bug rule: never modify a list mid-loop - build a
new one. Next lesson: dictionaries and sets, where device inventories stop
being parallel lists and start being real data structures.