Dictionaries, Sets, and Exceptions

Parallel lists stop scaling the moment a device has more than one fact. Dictionaries give your data names, sets answer "what's different between these two switches" in one operator, and exceptions keep one bad input from killing a 300-device run.

In this lesson you will:
  • Model device inventories as dictionaries instead of parallel lists
  • Read nested dict structures — the shape every network API returns
  • Use .get() to make missing keys a decision instead of a crash
  • Detect config drift with set difference in one line
  • Catch specific exceptions so bad input skips instead of kills

Dictionaries: data with names

Three lessons in, you could track an inventory as parallel lists — hostnames in one, IPs in another, models in a third — and pray the indexes stay aligned. Don’t. A dictionary keeps each fact attached to its name:

sw1 = {
    "hostname": "den-acc-sw01",
    "ip": "10.20.30.11",
    "model": "C9200L-48P-4X",
}

Read values by key, write the same way:

>>> sw1["ip"]
'10.20.30.11'
>>> sw1["serial"] = "FOC2217A0AB"     # add a new key
>>> "model" in sw1                     # membership checks the KEYS
True

Ask for a key that isn’t there with square brackets and you get a KeyError — a crash. The professional habit is .get(), which makes a missing key a decision instead:

>>> sw1.get("uptime", "unknown")
'unknown'

That one method is the difference between an inventory report with a few “unknown” cells and a script that dies at device 247 of 300 because one switch never reported uptime.

Real inventories nest

One device is a dict; a fleet is a dict of dicts, keyed by hostname:

inventory = {
    "den-acc-sw01": {"ip": "10.20.30.11", "model": "C9200L-48P-4X"},
    "den-acc-sw02": {"ip": "10.20.30.12", "model": "C9200L-48P-4X"},
}

>>> inventory["den-acc-sw01"]["ip"]    # read inside-out, left to right
'10.20.30.11'

Loop a dict with .items(), which hands you key and value together:

for hostname, facts in inventory.items():
    print(f"{hostname:<16} {facts['ip']:<14} {facts['model']}")

Get comfortable with this shape now — every network API you will ever call returns nested dictionaries. When you hit REST, NETCONF, and structured show output in the flagship course, the payloads are this, three levels deeper.

Sets: the drift detector

A set is an unordered collection of unique values. Deduplication is the warm-up trick:

>>> seen = [10, 20, 10, 30, 20]
>>> set(seen)
{10, 20, 30}

The real power is the operators. Subtract one set from another and you get what’s in the first but not the second:

>>> intended = {10, 20, 30, 40}      # what the design says
>>> actual = {10, 20, 50}            # what the switch says
>>> intended - actual                # missing from the switch
{30, 40}
>>> actual - intended                # rogue — on the switch, not the design
{50}
>>> intended & actual                # in both
{10, 20}

That’s config drift detection — the entire concept — in three lines.

Exceptions: one bad input shouldn’t kill the run

You’ve already met exceptions — KeyError, ValueError, the AttributeError from Lesson 1’s .strip typo. So far they’ve meant a dead script. try/except turns them into handled cases:

raw = "twenty"                  # parsed from somewhere ugly

try:
    vlan = int(raw)
except ValueError:
    print(f"skipping unparseable VLAN: {raw!r}")
    vlan = None

The block under try is the risky part; the block under except runs only if that specific exception fires. At fleet scale this is survival: when device 113’s output is malformed, you want one logged skip — not three hours of work lost at 2 AM.

Catch the specific exception you expect. The lazy form:

try:
    vlan = int(raw)
except:                # ← catches EVERYTHING, including your own typos
    vlan = None

For dict lookups you now have two idioms — .get() when a default is fine, try/except KeyError when a missing key needs real handling (logging, counting, alerting). Both beat the crash.

🖥 Inventories, drift detection, and graceful failure
▶ Try it yourself (Python runs in your browser)
Output appears here. First run downloads the Python runtime (~10 MB), so give it a few seconds.

Exercises (graded)

cd labs/python-foundations/lesson04
pytest -q

Five functions in exercises.py:

  1. device_facts(hostname, ip, model) — build the standard device dict
  2. safe_get_ip(inventory, hostname) — nested lookup that returns None instead of crashing
  3. unique_vlans(vlan_lists) — collapse many VLAN lists into one sorted, deduped list
  4. missing_vlans(intended, actual) — the drift report: sorted list of what’s missing
  5. parse_vlan_id(text)int() with a safety net: None for garbage input
✅ Check your understanding

Your report script crashes with KeyError: "serial" halfway through the fleet. Which change makes it finish, marking unknowns?

1 / 3

Summary

Dictionaries attach names to facts — sw1["ip"], .get() for the keys that might not be there, .items() for looping, and nesting for fleets, which is the exact shape every API response takes. Sets answer membership questions at a glance: dedupe with set(), and read config drift straight off the subtraction operator (intended - actual). Exceptions make failure a handled case — catch the specific error you expect (ValueError, KeyError) and let everything else crash loudly, because a bare except: converts your own bugs into silent wrong answers. Next lesson: regular expressions — pulling structured facts out of show output that .split() can’t reach.