Python: Traversing and Inspecting Nested Dictionaries

Working with nested dictionary structures is common in Python, especially when dealing with data from APIs, configuration files (e.g., JSON), or complex objects. Inspecting these structures can be challenging. This document provides a recursive Python function, walk_dict, designed to traverse and print the contents of nested dictionaries in a human-readable, indented format.

1. The walk_dict Function

This recursive function iterates through a dictionary, printing keys and values. If a value is itself a dictionary, it calls itself to further explore that nested structure, incrementing the indentation level for visual clarity.

def walk_dict(d, indent=0):
    """
    Recursively walks through a nested dictionary and prints its contents
    with indentation for better readability.
 
    Args:
        d (dict): The dictionary to traverse.
        indent (int): The current indentation level (for pretty printing).
    """
    for key, value in d.items():
        # Print current key with appropriate indentation
        print('  ' * indent + f'{key}:', end=' ')
 
        if isinstance(value, dict):
            # If the value is a dictionary, print a newline and recurse
            print()
            walk_dict(value, indent + 1)
        else:
            # Otherwise, print the value
            print(value)

How it Works:

  • for key, value in d.items():: Iterates over each key-value pair in the current dictionary d.
  • print(' ' * indent + f'{key}:', end=' '): Prints the key, prefixed by indent spaces for visual hierarchy. end=' ' keeps the value on the same line if it’s not a nested dictionary.
  • if isinstance(value, dict):: Checks if the current value is another dictionary.
  • walk_dict(value, indent + 1): If it’s a dictionary, the function calls itself (recursion) with the nested dictionary and an increased indent level.
  • else: print(value): If the value is not a dictionary, it’s printed directly.

2. Example Usage: Inspecting Nmap Scan Results

This function is particularly useful for exploring complex data structures returned by libraries or tools, such as the scan results from python-nmap.

import nmap # Assuming python-nmap is installed and imported
 
# Example use (assuming a previous nmap scan result is stored in 'nm'):
# nm = nmap.PortScanner()
# nm.scan(hosts='127.0.0.1', arguments='-p 22-80 -sV')
 
# Iterate through all scanned hosts
nm = nmap.PortScanner()
nm.scan('localhost', '22-443')
for host in nm.all_hosts():
    print(f'\n--- Host: {host} ({nm[host].hostname()}) ---')
    # Pass the dictionary for the current host to the walk_dict function
    walk_dict(nm[host])
  • nm.all_hosts(): Returns a list of all IP addresses that were scanned.
  • nm[host]: Accesses the dictionary containing all scan details for a specific host.

This example demonstrates how walk_dict can be used to pretty-print the hierarchical results of an Nmap scan, making it easier to navigate the host’s details, protocols, ports, and service information.

3. Alternative for Pretty-Printing

For general JSON-like dictionary structures, Python’s json module offers a convenient way to pretty-print.

import json
 
my_nested_dict = {
    "user": {
        "name": "Alice",
        "details": {
            "age": 30,
            "city": "New York"
        }
    },
    "preferences": ["dark_mode", "notifications"]
}
 
print(json.dumps(my_nested_dict, indent=2))

While json.dumps is great for simple JSON-compatible data, walk_dict provides more control over the output format and can handle non-JSON-serializable data types if needed.