Reading and Interpreting Python Scripts
1. Why Python Matters for CCNA
The CCNA exam introduced Python awareness to reflect the industry shift toward network automation. Network engineers increasingly use Python to configure devices, parse CLI output, generate reports, and automate repetitive tasks — work that used to take hours of manual CLI commands now runs in seconds.
The CCNA does not require you to write complex Python — it requires you to read and interpret simple scripts: understand what a script does, predict its output, and recognise common constructs like loops, functions, and dictionaries.
Traditional networking: Automated networking:
Engineer → CLI → Router Python script → SSH → Router
(one device, one command) (100 devices, all commands, 5 seconds)
Related pages: Network Protocols | SSH | SNMP | NTP | Network Automation Overview | Python for Networking | Ansible Overview | Python Netmiko Lab | Python NAPALM Lab
2. Basic Python Syntax
Indentation — Python's Block Structure
Python uses indentation (spaces or tabs) to define code blocks — there are no curly braces { }. This is the single most important syntax rule: wrong indentation causes an IndentationError and the script won't run. The standard is 4 spaces per indent level.
# Correct indentation
if 5 > 3:
print("Five is greater") # This is inside the if block (4 spaces)
print("Still inside") # Also inside the if block
print("Outside the if block") # Back at column 0 = outside
# Wrong indentation — causes IndentationError
if 5 > 3:
print("Error!") # No indentation = IndentationError
Comments
Lines starting with # are comments — Python ignores them completely. They document what code does and are never executed. Reading comments is the fastest way to understand what an unfamiliar script is trying to accomplish.
# This entire line is a comment — not executed
x = 10 # Inline comment — only the part after # is ignored
print(x) # Outputs: 10
Python vs Other Languages — Key Differences
| Feature | Python | Other Languages (C, Java) |
|---|---|---|
| Code blocks | Indentation (4 spaces) | Curly braces { } |
| Statement end | Newline (no semicolons needed) | Semicolon ; required |
| Variable declaration | None — just assign (x = 5) | Type declaration required (int x = 5;) |
| String quotes | Both "double" and 'single' work | Often distinct |
| Case sensitivity | Yes — Name ≠ name | Usually yes |
3. Variables and Data Types
Python variables are created by assignment — no type declaration needed. Python automatically detects the type based on the value assigned.
| Type | Description | Example | Notes |
|---|---|---|---|
| int | Whole number | age = 28 | No decimal point |
| float | Decimal number | height = 1.75 | Has a decimal point |
| str | Text string | name = "John" | In quotes (single or double) |
| bool | True or False | is_active = True | Capital T/F; used in conditions |
| list | Ordered, changeable collection | skills = ["Python", "Linux"] | Square brackets; zero-indexed |
| dict | Key-value pairs — ideal for device data | device = {"host": "192.168.1.1", "type": "router"} | Curly braces; access by key. Also used for JSON data. |
| tuple | Ordered, unchangeable collection | coords = (10, 20) | Round brackets; immutable |
| NoneType | Absence of value | result = None | Like NULL in other languages |
# Working with a list
skills = ["Python", "Networking", "Linux"]
print(skills[0]) # "Python" — first item (index 0)
print(skills[-1]) # "Linux" — last item (negative index)
print(skills[1:3]) # ["Networking", "Linux"] — slice (index 1 up to but not including 3)
print(len(skills)) # 3 — number of items
# Working with a dictionary
device = {"host": "192.168.1.1", "vendor": "Cisco", "type": "router"}
print(device["host"]) # "192.168.1.1" — access by key
print(device.get("port", 22)) # 22 — get() with default value if key missing
device["location"] = "HQ" # Add a new key-value pair
4. Operators
| Category | Operators | Example | Result |
|---|---|---|---|
| Arithmetic | + - * / // % ** | 10 // 3 / 10 % 3 / 2 ** 3 | 3 (floor div) / 1 (remainder) / 8 (power) |
| Comparison | == != < > <= >= | 5 == 5 / 5 != 3 | True / True |
| Logical | and or not | True and False / True or False | False / True |
| Assignment | = += -= *= | x = 5; x += 3 | x is now 8 |
| Membership | in not in | "Python" in skills | True (if "Python" is in the list) |
# Logical operators in practice
x = 10
print(x > 5 and x < 20) # True — both conditions must be True
print(x < 5 or x > 8) # True — at least one condition is True
print(not x == 10) # False — reverses the boolean result
# Membership operator — very useful for checking list/dict contents
protocols = ["TCP", "UDP", "ICMP"]
if "TCP" in protocols:
print("TCP is supported") # This runs
5. Input and Output
# Output — print() can display multiple values with a separator
print("Hello, John")
print("IP:", "192.168.1.1", "Port:", 22) # IP: 192.168.1.1 Port: 22
print(f"Device {device['host']} is online") # f-string formatting
# Input — always returns a string; convert with int() or float() if needed
host = input("Enter device IP: ") # User types: 192.168.1.1
timeout = int(input("Enter timeout (s): ")) # Convert to integer
print(f"Connecting to {host} with {timeout}s timeout")
input() always returns a string, even if the user types a number. To use the value as a number, convert it: int(input("...")) or float(input("...")). Forgetting this causes TypeError when you try to do arithmetic on the result.6. Control Flow — if / elif / else
Conditional statements let a script make decisions. Python evaluates each condition in order and executes only the first matching block.
# Basic if/elif/else
interface_status = "up"
if interface_status == "up":
print("Interface is operational")
elif interface_status == "down":
print("Interface is down — check cable")
elif interface_status == "admin-down":
print("Interface was shut down by admin")
else:
print("Unknown status")
# Checking numeric ranges
latency_ms = 45
if latency_ms < 20:
print("Excellent")
elif latency_ms < 100:
print("Acceptable") # This runs (45 is between 20 and 100)
else:
print("High latency — investigate")
The condition after if or elif must evaluate to True or False. Comparison operators (==, !=, <, >) all produce boolean results.
7. Loops — for and while
for Loops — Iterating Over a Sequence
# Iterate over a list
devices = ["router1", "switch1", "switch2"]
for device in devices:
print("Connecting to:", device)
# Output:
# Connecting to: router1
# Connecting to: switch1
# Connecting to: switch2
# range() — generate a sequence of numbers
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(1, 6): # 1, 2, 3, 4, 5
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8 (step = 2)
print(i)
# Iterate over a dictionary
config = {"hostname": "R1", "ip": "10.0.0.1", "mask": "255.255.255.0"}
for key, value in config.items():
print(f"{key}: {value}")
while Loops — Repeat While a Condition Is True
# Count down from 3
countdown = 3
while countdown > 0:
print("Count:", countdown)
countdown -= 1 # Equivalent to: countdown = countdown - 1
print("Done!")
# Output: Count: 3 / Count: 2 / Count: 1 / Done!
# Retry logic — common pattern in network automation scripts
attempts = 0
max_retries = 3
while attempts < max_retries:
print(f"Attempt {attempts + 1}: Connecting...")
attempts += 1
# In real code: break out of loop if connection succeeds
while loop whose condition never becomes False runs forever. Always ensure the loop variable changes in the body — countdown -= 1 in the example above. Use break to exit a loop early, and continue to skip to the next iteration.8. Functions — def and return
Functions group reusable code under a name. In network automation scripts, functions typically wrap common operations like connecting to a device via SSH, sending a command, or parsing output from show interfaces or show ip route.
# Define a function with parameters
def check_ping(host, count=4): # count has a default value of 4
"""
Checks if a host is reachable by ping.
host: IP address or hostname (string)
count: number of pings to send (int, default 4)
"""
print(f"Pinging {host} {count} times...")
# (real code would use subprocess or a library)
return True # Simulated success
# Call the function
result = check_ping("192.168.1.1") # Uses default count=4
result2 = check_ping("10.0.0.1", 2) # Overrides count with 2
if result:
print("Host is reachable")
# Function with return value — calculation example
def calculate_subnet_hosts(prefix_length):
"""Returns the number of usable hosts for a given prefix length."""
total = 2 ** (32 - prefix_length)
return total - 2 # Subtract network and broadcast
hosts = calculate_subnet_hosts(24)
print(f"/24 has {hosts} usable hosts") # /24 has 254 usable hosts
Key rules: A function must be defined before it is called. Parameters in the function definition are local — they only exist inside the function. Use return to send a value back to the caller. Functions without return return None automatically.
9. Error Handling — try / except
Network scripts inevitably encounter errors: devices are unreachable, SSH credentials fail, timeout occurs, output format is unexpected. The try/except block lets the script handle these gracefully instead of crashing.
# Basic try/except
try:
num = int(input("Enter a number: ")) # Could fail if user types letters
result = 100 / num # Could fail if num is 0
print("Result:", result)
except ValueError:
print("Error: Please enter a valid number")
except ZeroDivisionError:
print("Error: Cannot divide by zero")
except Exception as e:
print(f"Unexpected error: {e}") # Catches anything else
finally:
print("This always runs — use for cleanup")
# Common networking error pattern
import socket
try:
socket.create_connection(("192.168.1.1", 22), timeout=3)
print("SSH port is open")
except socket.timeout:
print("Connection timed out — device unreachable")
except ConnectionRefusedError:
print("Port 22 closed or SSH not running")
| Exception | When It Occurs |
|---|---|
ValueError | Wrong type conversion — int("abc") |
ZeroDivisionError | Division by zero — 10 / 0 |
KeyError | Dictionary key doesn't exist — d["missing"] |
IndexError | List index out of range — lst[99] on a short list |
FileNotFoundError | Opening a file that doesn't exist |
Exception | Catches any exception (use as a last resort catch-all) |
10. Importing Modules
Python's power for network automation comes from its rich library ecosystem. The import statement loads a module (built-in, installed, or custom) making its functions and classes available in your script.
# Import an entire module
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.14159...
# Import specific items from a module
from os import getcwd, listdir
print(getcwd()) # Current working directory
# Import with an alias (shorter name)
import json as j
data = j.loads('{"host": "192.168.1.1"}')
print(data["host"]) # 192.168.1.1
# Networking-relevant standard library modules
import socket # Low-level network connections
import subprocess # Run OS commands (ping, traceroute)
import re # Regular expressions for parsing CLI output
import json # Parse JSON-format API responses — see JSON, XML & YANG
import time # Delays between commands (time.sleep(1))
The json module is particularly important for parsing responses from REST APIs and RESTCONF/NETCONF interfaces. See JSON, XML & YANG for the data format context, and REST API Overview for how Python scripts interact with network device APIs.
| Library | Purpose | Install |
|---|---|---|
| Netmiko | SSH to Cisco/multi-vendor devices; send commands and parse output. See Python Netmiko Lab. | pip install netmiko |
| Paramiko | Low-level SSH library — foundation for Netmiko | pip install paramiko |
| NAPALM | Vendor-neutral network device management API. See Python NAPALM Lab. | pip install napalm |
| requests | HTTP/REST API calls — for Cisco DNA Center, Meraki, NSO. Used with RESTCONF. | pip install requests |
| nmap | Network scanning from Python | pip install python-nmap |
11. Common Built-in Functions and Data Manipulation
| Function | Purpose | Example | Output |
|---|---|---|---|
len() | Number of items in a list, string, or dict | len(["a", "b", "c"]) | 3 |
range() | Generate a numeric sequence | list(range(1, 4)) | [1, 2, 3] |
type() | Get the data type of a variable | type(3.14) | <class 'float'> |
int() | Convert to integer | int("42") | 42 |
str() | Convert to string | str(192) | "192" |
print() | Output to screen | print("IP:", ip) | IP: 10.0.0.1 |
sorted() | Return sorted list | sorted([3,1,2]) | [1, 2, 3] |
enumerate() | Loop with index and value | for i, v in enumerate(lst) | i=0,v=item1 etc. |
String Operations
hostname = "router-01.hq.example.com"
print(hostname.upper()) # "ROUTER-01.HQ.EXAMPLE.COM"
print(hostname.lower()) # "router-01.hq.example.com"
print(hostname.split(".")) # ["router-01", "hq", "example", "com"]
print(hostname.startswith("router")) # True
print(hostname.replace("-", "_")) # "router_01.hq.example.com"
print(f"Hostname: {hostname}") # f-string: inserts variable into text
List and Dictionary Operations
# List operations
interfaces = ["Gi0/0", "Gi0/1", "Gi0/2"]
interfaces.append("Gi0/3") # Add item to end
interfaces.remove("Gi0/1") # Remove specific item
print(interfaces[0]) # "Gi0/0" — first item (index 0)
print(interfaces[-1]) # "Gi0/3" — last item
# Dictionary operations — similar structure to JSON API responses
device = {"hostname": "R1", "ip": "10.0.0.1"}
device["os"] = "IOS-XE" # Add new key
print(device.keys()) # dict_keys(["hostname", "ip", "os"])
print(device.values()) # dict_values(["R1", "10.0.0.1", "IOS-XE"])
print("hostname" in device) # True — key exists check
Python dictionaries map directly to JSON objects — when you receive a response from a REST API or RESTCONF endpoint, json.loads() converts it to a Python dictionary you can loop over and query by key.
12. Reading a Complete Networking Script
Here is a realistic script showing how all the concepts work together. Read through it and use the annotation to understand each part. This pattern — checking SSH port reachability across a list of devices — is a common first step in network automation. For a full Netmiko-based implementation, see Python Netmiko Lab.
# network_checker.py
# Purpose: Check reachability of a list of network devices
# Author: NetsTuts Team
import socket # 1. Import standard library
import time # 2. For delays between checks
def check_ssh_port(host, port=22, timeout=3):
"""
Tests if SSH port is open on a device.
Returns True if reachable, False if not.
"""
try: # 3. Error handling
conn = socket.create_connection((host, port), timeout=timeout)
conn.close()
return True # 4. Return value
except (socket.timeout, ConnectionRefusedError, OSError):
return False
# 5. List of devices to check
devices = [
{"name": "Core-Router", "ip": "10.0.0.1"},
{"name": "Dist-Switch", "ip": "10.0.0.2"},
{"name": "Access-SW1", "ip": "10.0.1.1"},
]
reachable = [] # 6. Empty list to collect results
unreachable = []
for device in devices: # 7. Loop over list of dicts
name = device["name"] # 8. Access dict values by key
ip = device["ip"]
if check_ssh_port(ip): # 9. Call function, check result
reachable.append(name) # 10. Append to list
print(f" OK {name} ({ip})")
else:
unreachable.append(name)
print(f" !! {name} ({ip}) UNREACHABLE")
time.sleep(0.5) # 11. Wait 0.5 seconds between checks
# 12. Summary output
print(f"\nReachable: {len(reachable)} devices")
print(f"Unreachable: {len(unreachable)} devices")
if unreachable:
print("Devices to investigate:", unreachable)
- import socket — loads the socket module for network connections
- import time — loads the time module for sleep/delay
- try/except — handles connection failures gracefully without crashing
- return True/False — function sends result back to the caller
- devices list — a list of dictionaries, each describing one device
- reachable = [] — empty list initialised before the loop
- for device in devices — iterates over each dictionary in the list
- device["name"] — accesses a value from the current dict by key
- if check_ssh_port(ip): — calls the function; True = device reachable
- reachable.append(name) — adds the device name to the results list
- time.sleep(0.5) — pauses 0.5 seconds to avoid overwhelming devices
- len(reachable) — counts items in the list for the summary
13. Key Points and Exam Tips
- Indentation is mandatory — Python uses 4 spaces per level. Mixing tabs and spaces causes errors. Wrong indentation changes what code is inside a block.
- List indexing starts at 0 —
skills[0]is the first item,skills[-1]is the last. - Dictionary access by key —
device["hostname"]returns the value for key "hostname". Using a non-existent key raisesKeyError; use.get("key", default)to avoid this. Dictionaries are the Python equivalent of JSON objects. - range(n) produces 0 to n-1 —
range(3)gives0, 1, 2(not 1, 2, 3). - Functions must be defined before calling — Python reads files top to bottom.
- input() always returns a string — convert with
int()orfloat()for numeric operations. - Type matters —
"5" + "3"gives"53"(string concatenation);5 + 3gives8(addition). - Read comments first — when interpreting an unfamiliar script, start by reading all
#comments to understand the purpose before reading the code. - Python scripts use SSH (via Netmiko/Paramiko) or REST APIs (via requests) to interact with network devices. See Python for Networking for the broader context.
- For hands-on automation: Python Netmiko Lab | Python NAPALM Lab | Ansible IOS Lab.