Data Structures
Collections are everywhere in infrastructure automation. Master them for API responses, config parsing, and data transformation.
Lists
Creating Lists
# Empty list
hosts = []
# With values
hosts = ["ise-01", "ise-02", "ise-03"]
# From range
vlans = list(range(10, 20)) # [10, 11, ..., 19]
# From string
chars = list("hello") # ['h', 'e', 'l', 'l', 'o']
Accessing Elements
hosts = ["ise-01", "ise-02", "ise-03", "ise-04"]
# By index (0-based)
hosts[0] # "ise-01"
hosts[1] # "ise-02"
hosts[-1] # "ise-04" (last)
hosts[-2] # "ise-03" (second to last)
# Slicing [start:end:step]
hosts[1:3] # ["ise-02", "ise-03"]
hosts[:2] # ["ise-01", "ise-02"] (first 2)
hosts[2:] # ["ise-03", "ise-04"] (from index 2)
hosts[::2] # ["ise-01", "ise-03"] (every other)
hosts[::-1] # Reversed
Modifying Lists
hosts = ["ise-01", "ise-02"]
# Append (add to end)
hosts.append("ise-03") # ["ise-01", "ise-02", "ise-03"]
# Insert at position
hosts.insert(0, "ise-pan") # ["ise-pan", "ise-01", ...]
# Extend (add multiple)
hosts.extend(["ise-04", "ise-05"])
# Remove by value
hosts.remove("ise-02")
# Remove by index
del hosts[0]
popped = hosts.pop() # Remove and return last
popped = hosts.pop(0) # Remove and return first
# Clear all
hosts.clear()
# Sort
hosts = ["ise-03", "ise-01", "ise-02"]
hosts.sort() # In-place: ["ise-01", "ise-02", "ise-03"]
hosts.sort(reverse=True) # Descending
# Sorted (returns new list)
sorted_hosts = sorted(hosts)
List Operations
hosts = ["ise-01", "ise-02", "ise-03"]
# Length
len(hosts) # 3
# Check membership
"ise-01" in hosts # True
"ise-99" not in hosts # True
# Count occurrences
hosts.count("ise-01") # 1
# Find index
hosts.index("ise-02") # 1
# Copy (shallow)
hosts_copy = hosts.copy()
hosts_copy = hosts[:]
hosts_copy = list(hosts)
# Concatenate
all_hosts = hosts + ["wlc-01", "wlc-02"]
Dictionaries
Creating Dicts
# Empty dict
config = {}
# With values
config = {
"hostname": "ise-01",
"ip": "10.50.1.20",
"port": 443
}
# From tuples
config = dict([("hostname", "ise-01"), ("ip", "10.50.1.20")])
# From keys with default value
hosts = dict.fromkeys(["ise-01", "ise-02"], "active")
# {"ise-01": "active", "ise-02": "active"}
Accessing Values
config = {"hostname": "ise-01", "ip": "10.50.1.20", "port": 443}
# By key
config["hostname"] # "ise-01"
# With default (no error if missing)
config.get("hostname") # "ise-01"
config.get("timeout") # None
config.get("timeout", 30) # 30 (default)
# Keys, values, items
config.keys() # dict_keys(['hostname', 'ip', 'port'])
config.values() # dict_values(['ise-01', '10.50.1.20', 443])
config.items() # dict_items([('hostname', 'ise-01'), ...])
Modifying Dicts
config = {"hostname": "ise-01"}
# Add/update single
config["ip"] = "10.50.1.20"
# Update multiple
config.update({"port": 443, "timeout": 30})
# Set default (only if key doesn't exist)
config.setdefault("protocol", "https") # Adds it
config.setdefault("hostname", "new") # Does nothing
# Remove
del config["timeout"]
port = config.pop("port") # Remove and return
port = config.pop("port", None) # With default
# Remove last inserted (Python 3.7+)
key, value = config.popitem()
Nested Dicts
# Common in API responses
deployment = {
"nodes": {
"ise-01": {
"ip": "10.50.1.20",
"roles": ["PAN", "MNT"],
"status": "running"
},
"ise-02": {
"ip": "10.50.1.21",
"roles": ["PSN"],
"status": "running"
}
},
"version": "3.2"
}
# Access nested
deployment["nodes"]["ise-01"]["ip"] # "10.50.1.20"
# Safe nested access
def get_nested(d, *keys, default=None):
for key in keys:
if isinstance(d, dict):
d = d.get(key, default)
else:
return default
return d
get_nested(deployment, "nodes", "ise-01", "ip") # "10.50.1.20"
get_nested(deployment, "nodes", "ise-99", "ip") # None
Sets
Creating Sets
# Empty set (NOT {} - that's a dict)
vlans = set()
# With values
vlans = {10, 20, 30}
# From list (deduplicates)
vlans = set([10, 20, 20, 30]) # {10, 20, 30}
Set Operations
allowed = {10, 20, 30}
active = {20, 30, 40}
# Union (all from both)
allowed | active # {10, 20, 30, 40}
allowed.union(active)
# Intersection (in both)
allowed & active # {20, 30}
allowed.intersection(active)
# Difference (in first, not in second)
allowed - active # {10}
allowed.difference(active)
# Symmetric difference (in one or other, not both)
allowed ^ active # {10, 40}
# Membership
20 in allowed # True
50 in allowed # False
# Subset/superset
{10, 20} <= allowed # True (subset)
{10, 20} < allowed # True (proper subset)
allowed >= {10, 20} # True (superset)
Practical: Find Rogue VLANs
# VLANs configured on switch
configured_vlans = {10, 20, 30, 40, 50}
# VLANs allowed by policy
allowed_vlans = {10, 20, 30}
# Find unauthorized VLANs
rogue_vlans = configured_vlans - allowed_vlans
print(f"Rogue VLANs: {rogue_vlans}") # {40, 50}
# Find missing VLANs
missing_vlans = allowed_vlans - configured_vlans
print(f"Missing VLANs: {missing_vlans}") # set()
Tuples
Immutable Sequences
# Create tuple
point = (10, 20)
host_info = ("ise-01", "10.50.1.20", 443)
# Single element (needs comma)
single = (42,)
# Access (like lists)
host_info[0] # "ise-01"
host_info[-1] # 443
# Unpack
hostname, ip, port = host_info
# Partial unpack
hostname, *rest = host_info # hostname="ise-01", rest=["10.50.1.20", 443]
first, *middle, last = [1, 2, 3, 4, 5] # first=1, middle=[2,3,4], last=5
# Named tuples (better)
from collections import namedtuple
Host = namedtuple("Host", ["hostname", "ip", "port"])
ise = Host("ise-01", "10.50.1.20", 443)
ise.hostname # "ise-01"
ise.ip # "10.50.1.20"
ise[0] # "ise-01" (still indexable)
Comprehensions
List Comprehensions
# Basic: [expression for item in iterable]
hosts = ["ise-01", "ise-02", "ise-03"]
upper_hosts = [h.upper() for h in hosts]
# With condition: [expression for item in iterable if condition]
active_hosts = [h for h in hosts if is_active(h)]
# Multiple conditions
valid = [h for h in hosts if h.startswith("ise") and is_active(h)]
# Nested loops
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [num for row in matrix for num in row] # [1, 2, 3, 4, 5, 6]
# With if/else (note: different position)
status = ["active" if is_active(h) else "inactive" for h in hosts]
Dict Comprehensions
# Basic: {key_expr: value_expr for item in iterable}
hosts = ["ise-01", "ise-02", "ise-03"]
host_status = {h: "active" for h in hosts}
# From two lists
hostnames = ["ise-01", "ise-02"]
ips = ["10.50.1.20", "10.50.1.21"]
host_map = {h: ip for h, ip in zip(hostnames, ips)}
# Filter dict
config = {"hostname": "ise-01", "ip": "10.50.1.20", "secret": "password"}
safe_config = {k: v for k, v in config.items() if k != "secret"}
# Transform values
config = {"port": "443", "timeout": "30"}
int_config = {k: int(v) for k, v in config.items()}
# Swap keys and values
inverted = {v: k for k, v in config.items()}
Set Comprehensions
# Extract unique domains from hostnames
hosts = ["ise-01.inside.domusdigitalis.dev", "wlc-01.inside.domusdigitalis.dev"]
domains = {h.split(".", 1)[1] for h in hosts} # {"inside.domusdigitalis.dev"}
# Filter to unique VLANs
ports = [{"vlan": 10}, {"vlan": 20}, {"vlan": 10}]
unique_vlans = {p["vlan"] for p in ports} # {10, 20}
Generator Expressions
# Like list comprehension but lazy (memory efficient)
# Use () instead of []
# Sum of large range (doesn't create list in memory)
total = sum(i for i in range(1_000_000))
# Any/all with condition
hosts = ["ise-01", "ise-02", "wlc-01"]
any_ise = any(h.startswith("ise") for h in hosts) # True
all_ise = all(h.startswith("ise") for h in hosts) # False
# First match
first_ise = next((h for h in hosts if h.startswith("ise")), None)
Practical Patterns
Parse API Response
# ISE deployment response
response = {
"response": [
{"hostname": "ise-01", "status": "running", "roles": ["PAN"]},
{"hostname": "ise-02", "status": "running", "roles": ["PSN"]},
{"hostname": "ise-03", "status": "stopped", "roles": ["PSN"]}
]
}
# Extract running PSNs
running_psns = [
node["hostname"]
for node in response["response"]
if node["status"] == "running" and "PSN" in node["roles"]
]
# ["ise-02"]
# Create status map
status_map = {
node["hostname"]: node["status"]
for node in response["response"]
}
# {"ise-01": "running", "ise-02": "running", "ise-03": "stopped"}
Group By
from collections import defaultdict
endpoints = [
{"mac": "00:11:22:33:44:55", "vlan": 10},
{"mac": "00:11:22:33:44:56", "vlan": 20},
{"mac": "00:11:22:33:44:57", "vlan": 10},
]
# Group endpoints by VLAN
by_vlan = defaultdict(list)
for ep in endpoints:
by_vlan[ep["vlan"]].append(ep["mac"])
# {10: ["00:11:22:33:44:55", "00:11:22:33:44:57"], 20: ["00:11:22:33:44:56"]}
Next Module
Functions - Definitions, arguments, closures, and decorators.