This post is part of an ongoing series - see all the posts below.
We have already used lists and dictionaries quite a few times in this series. We saw them briefly in Chapter 1 when we set up our Netmiko script, and again in Chapter 4 when we looped through lists of devices. They are easily two of the most important data types in Python, especially when it comes to network automation.
Almost every script you write will involve lists or dictionaries in some way. A list of devices to connect to, a dictionary of connection details, a list of interfaces returned from a device, a dictionary of configuration values, the output of an API call (which is just dictionaries and lists in a different wrapper), it all comes back to these two data types. If you get comfortable with them now, the rest of the series, and your network automation journey in general, will feel a lot smoother.
In this chapter, we will go deeper into how lists and dictionaries actually work. We will cover how to add, remove, and update items, the most useful built-in methods, and how to access data in nested structures (like a list of dictionaries, or a dictionary with lists inside it). We will wrap up with a small practical example where we build a simple device inventory and do useful things with it.
Lists in Detail
A list is an ordered collection of items, written with square brackets, with items separated by commas. When we say "ordered", we mean that the items stay in the order you put them in. If you add three devices in a certain order, they will always come back in that same order. Each item also has a fixed position, so you can access it by its index, which we will look at in a moment.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
Lists can hold any type of value, strings, numbers, booleans, even other lists or dictionaries. The items in a list do not all have to be the same type, although in practice you usually want them to be.
mixed = ["r1_lnd_dc", 100, True, 23.5]
Accessing Items by Index
Each item in a list has a position called an index. Just like with strings, Python starts counting from zero, so the first item is at index 0, the second at index 1, and so on.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
print(devices[0])
print(devices[1])
print(devices[2])
r1_lnd_dc
r2_lnd_dc
r3_lnd_dc
You can also use negative indexing to count backwards from the end. -1 gives you the last item, -2 the second to last, and so on.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
print(devices[-1])
print(devices[-2])
r3_lnd_dc
r2_lnd_dc
If you try to access an index that does not exist, Python will throw an IndexError.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
print(devices[5])
IndexError: list index out of range
Slicing
Just like with strings, you can grab a portion of a list using slicing. The syntax is list[start:end], where start is included and end is not.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc", "r4_lnd_dc", "r5_lnd_dc"]
print(devices[0:2])
print(devices[2:4])
print(devices[:3])
print(devices[2:])
['r1_lnd_dc', 'r2_lnd_dc']
['r3_lnd_dc', 'r4_lnd_dc']
['r1_lnd_dc', 'r2_lnd_dc', 'r3_lnd_dc']
['r3_lnd_dc', 'r4_lnd_dc', 'r5_lnd_dc']
Slicing returns a new list, so you can store it in a variable or use it directly.
Adding Items to a List
There are a few ways to add items to a list. The most common one is .append(), which adds an item to the end.
devices = ["r1_lnd_dc", "r2_lnd_dc"]
devices.append("r3_lnd_dc")
print(devices)
['r1_lnd_dc', 'r2_lnd_dc', 'r3_lnd_dc']
If you want to add an item at a specific position, use .insert(). It takes two arguments, the position and the item.
devices = ["r1_lnd_dc", "r3_lnd_dc"]
devices.insert(1, "r2_lnd_dc")
print(devices)
['r1_lnd_dc', 'r2_lnd_dc', 'r3_lnd_dc']
If you want to add multiple items at once, use .extend(). It takes another list and adds all of its items to the end of your list.
devices = ["r1_lnd_dc", "r2_lnd_dc"]
devices.extend(["r3_lnd_dc", "r4_lnd_dc"])
print(devices)
['r1_lnd_dc', 'r2_lnd_dc', 'r3_lnd_dc', 'r4_lnd_dc']
A common mistake is using .append() when you mean .extend(). If you pass a list to .append(), you end up with a list inside a list, which is probably not what you wanted.
devices = ["r1_lnd_dc", "r2_lnd_dc"]
devices.append(["r3_lnd_dc", "r4_lnd_dc"])
print(devices)
['r1_lnd_dc', 'r2_lnd_dc', ['r3_lnd_dc', 'r4_lnd_dc']]
Removing Items from a List
To remove an item by its value, use .remove().
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
devices.remove("r2_lnd_dc")
print(devices)
['r1_lnd_dc', 'r3_lnd_dc']
If the item appears more than once, .remove() **only removes the first match. **If the item is not in the list at all, Python throws a ValueError.
To remove an item by its position, use .pop(). It also returns the item that was removed, which is sometimes handy.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
removed = devices.pop(1)
print(devices)
print(f"Removed: {removed}")
['r1_lnd_dc', 'r3_lnd_dc']
Removed: r2_lnd_dc
Here, .pop(1) removes the item at index 1 (which is r2_lnd_dc) from the list, and at the same time returns that value. We store it in a variable called removed, so we still have access to it even though it is no longer in the list. This is useful when you want to take an item out of one list and do something with it, like moving it to another list, logging it, or passing it to another function.
If you do not pass an index to .pop(), it removes and returns the last item.
You can also use the del keyword to delete an item by its index. Unlike .pop(), it does not return the removed item.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
del devices[0]
print(devices)
['r2_lnd_dc', 'r3_lnd_dc']
Updating Items
You can change an item in a list by assigning a new value to its index.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
devices[1] = "r2_lnd_dc_new"
print(devices)
['r1_lnd_dc', 'r2_lnd_dc_new', 'r3_lnd_dc']
Useful List Methods
A few more methods that come in handy.
.sort() sorts the list in place, meaning it changes the original list.
vlans = [100, 30, 50, 10, 200]
vlans.sort()
print(vlans)
[10, 30, 50, 100, 200]
.reverse() reverses the order of the list.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
devices.reverse()
print(devices)
['r3_lnd_dc', 'r2_lnd_dc', 'r1_lnd_dc']
.count() tells you how many times a value appears in a list.
statuses = ["up", "down", "up", "up", "down"]
print(statuses.count("up"))
3
.index() returns the position of the first match of a value.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
print(devices.index("r2_lnd_dc"))
1
len() (a function, not a method) gives you the number of items in the list.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
print(len(devices))
3
When I said len() is a function, not a method, you might be wondering how to tell the two apart. A method is called using a dot after the object, like devices.append("r4_lnd_dc"). A function is called on its own, with the object passed in as an argument inside the parentheses, like len(devices).
Checking if an Item is in a List
We saw this briefly with strings, but the in keyword works on lists too. It returns True or False.
devices = ["r1_lnd_dc", "r2_lnd_dc", "r3_lnd_dc"]
print("r1_lnd_dc" in devices)
print("r4_lnd_dc" in devices)
True
False
This is very useful when you want to check if a device, interface, or VLAN exists in a list before doing something with it.
Dictionaries in Detail
A dictionary is a collection of key-value pairs, written with curly braces, where each key is separated from its value by a colon, and each pair is separated by a comma.
device = {
"hostname": "r1_lnd_dc",
"ip": "192.168.1.1",
"vendor": "cisco",
"model": "ISR4321"
}
Unlike lists, dictionaries are not accessed by position. Instead, you access values using their keys. This makes dictionaries perfect for storing structured information where each piece of data has a name, like device details, configuration values, or API responses.
Keys are usually strings, but they can also be numbers or other immutable types. Values can be anything, strings, numbers, booleans, lists, even other dictionaries.
Accessing Values
To get a value from a dictionary, write the dictionary name followed by the key in square brackets.
device = {
"hostname": "r1_lnd_dc",
"ip": "192.168.1.1",
"vendor": "cisco"
}
print(device["hostname"])
print(device["ip"])
r1_lnd_dc
192.168.1.1
If you try to access a key that does not exist, Python throws a KeyError.
device = {
"hostname": "r1_lnd_dc",
"ip": "192.168.1.1"
}
print(device["vendor"])
KeyError: 'vendor'
This is one of the most common errors you will run into when working with dictionaries, especially when dealing with data from APIs where some keys might be missing. We will look at a safer way to handle this in a moment.
Adding and Updating Values
Adding a new key-value pair to a dictionary is as simple as assigning a value to a new key.
device = {
"hostname": "r1_lnd_dc",
"ip": "192.168.1.1"
}
device["vendor"] = "cisco"
device["model"] = "ISR4321"
print(device)
{'hostname': 'r1_lnd_dc', 'ip': '192.168.1.1', 'vendor': 'cisco', 'model': 'ISR4321'}
If the key already exists, the same syntax updates the value instead of creating a new pair.
device = {
"hostname": "r1_lnd_dc",
"ip": "192.168.1.1"
}
device["ip"] = "192.168.1.100"
print(device)
{'hostname': 'r1_lnd_dc', 'ip': '192.168.1.100'}
Python does not warn you when you overwrite an existing key, so be careful not to do it by accident.

