Hello my friend,
We continue our journey from Python to Go (Golang), or more right to say with Python and Go (Golang) together. Today we are going to talk about a data structure, which is by far the most widely used in Python when it comes to a network and IT infrastructure automation and management. This data structure is called dictionaries in Python, or Map in Go (Golang).
Black Friday Is Over, Can I Still Buy Your Trainings?
Of course, you can. Our self-paced network automation trainings are the perfect place to start your journey in network and IT infrastructure automation or to upskill yourself further if you are seasoned engineer. There is no such thing as excessive knowledge, therefore we encourage you to join our network automation programs and start your study today:
We offer the following training programs in network automation for you:
- Zero-to-Hero Network Automation Training
- High-scale automation with Nornir
- Ansible Automation Orchestration with Ansble Tower / AWX
- Expert-level training: Closed-loop Automation and Next-generation Monitoring
During these trainings you will learn the following topics:
- Success and failure strategies to build the automation tools.
- Principles of software developments and the most useful and convenient tools.
- Data encoding (free-text, XML, JSON, YAML, Protobuf).
- Model-driven network automation with YANG, NETCONF, RESTCONF, GNMI.
- Full configuration templating with Jinja2 based on the source of truth (NetBox).
- Best programming languages (Python, Bash) for developing automation
- The most rock-solid and functional tools for configuration management (Ansible) and Python-based automation frameworks (Nornir).
- Network automation infrastructure (Linux, Linux networking, KVM, Docker).
- Orchestration of automation workflows with AWX and its integration with NetBox, GitHub, as well as custom execution environments for better scalability.
- Collection network data via SNMP and streaming telemetry with Prometheus
- Building API gateways with Python leveraging Fast API
- Integration of alerting with Slack and your own APIs
- … and many more
Moreover, we put all mentions technologies in the context of real use cases, which our team has solved and are solving in various projects in the service providers, enterprise and data centre networks and systems across the Europe and USA. That gives you opportunity to ask questions to understand the solutions in-depth and have discussions about your own projects. And on top of that, each technology is provided with online demos and labs to master your skills thoroughly. Such a mixture creates a unique learning environment, which all students value so much. Join us and unleash your potential.
What Are We Going To Talk Today About?
I remember myself starting with Python long ago back in 2015 and how I was amazed by dictionaries. They are beautiful and truly powerful if you know when and how to use them. I hope you will be (or are) amazed as I am, and to make this happen we’ll discuss the following topics:
- What is dictionary/map in general?
- What are its similarities and differences from list? Where to use which data type?
- How to use dictionary/map in your Python and Go (Golang) code?
Explanation
In a nutshell, dictionary in Python is data structure consisting of key-value pairs, where key is typically a string (although it cloud be other data type, such as boolean, number and even tuple) and value is virtually anything: string, number, boolean, list, another dictionary or even function. From look and feel perspective it is very similar to list, but you have a convenience to use a meaningful name as indexes rather than just integers.
With Go (Golang) being statically typed, you have to specify upfront what data type you are going to use, and all your keys and variables must be of data type you define. There is a trick here, which called interfaces, which allows you to use any data type for value, but we’ll touch it in a separate blog post. By the say, Go (Golang) doesn’t use term dictionary, but it uses term map instead.
Other programming languages may use different terminology. For example, Perl uses term hash, whilst C doesn’t have such concept at all with hash-table being the closest data structure.
As we mentioned before, dictionaries are very similar to lists/slices in their capabilities and ways how we interact with them. As such, typical operations are:
- Use value of a specific key from the dictionary/map or of the entire one.
- Add/remove elements from dictionary/map
- Check the existence of the key in the dictionary/map
- Change the value of any existing key
In contrast to list/slice, you typically don’t sort order of keys in dictionary/map. In fact, in Python there is a dedicated data structure called OrderedDict, which intents to have keys ordered in whatever way. In Go (Golang), keys are by default sorted in alphabetical order, when strings are used.
As picture costs thousand words, let’s put all these concepts in motion.
Examples
The following scenario will serve the purpose of developing a demonstration piece of code both in Python and Go (Golang):
- You will have a list of dictionaries/maps, which acts as an inventory.
- First of all, we’ll loop through every element in this list to get access to these dictionaries/maps.
- For each dictionary/map we’ll perform all typical operations explained before.
As we are going to focus on dictionary part, we suggest you to review first list/slices and loop/conditionals blog posts from this series.
Python
Let’s review the sample code for Python first:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 """From Python to Go: Python: 005 - Dictionaries and Maps"""
# Variables
inventory = [
{
"name": "leaf-01",
"os": "cisco-nxos",
"ip": "192.168.1.1",
},
{
"name": "leaf-02",
"os": "arista-eos",
"ip": "192.168.1.2",
},
{
"name": "spine-01",
"ip": "192.168.1.1",
},
]
# Execution
if __name__ == "__main__":
# Loop through all network devices
for d in inventory:
# Print the device data
print(d)
# Print the hostname
print(f"Hostname: {d['name']}")
# Add the OS key if it is missing
if "os" not in d:
d["os"] = None
print(d)
# Add new key-value pair
d["location"] = "DC1"
print(d)
# Remove the IP key
d.pop("ip")
print(d)
# Go through all keys and values
for k, v in d.items():
print(f"{k}: {v}")
# Print the inventory after modifications
print(inventory)
The first thing you may see different is the notations of dictionary: its content is wrapped inside curly braces “{}“, which is different to content of the list, which is wrapped inside the square braces “[]“. The key-value pairs are provided in the format “‘key’: ‘value’” and are separated by comma from each other. We’ve put them one key-value pair at time line to simplify visual understanding. As the key is the sting, it is put in quotes, as any other string. The same syntax is in Go (Golang), by the way.
Some further explanations:
- In order to access specific key from the dictionary, you use “dict_name[“key_name”]” syntax. This is also applicable if you want to change content of the existing key or create a new key, but in these cases you also need to provide a new value after “=“.
- To delete the key from dictionary, you can use either “.pop()” method as provided or “del dict[“key_name”]” instruction.
- If you want to check, if certain key exists in dictionary in Python, you use if-conditional where you look literally for the name of the key within the dictionary: “if “key_name” not in dictionary” (you may also omit “not” depending on your logic).
- To loop through all the elements in the dictionary you use “.items()” method applied to dictionary. This method returns iterator with key and value elements in separate variables.
Let’s see the execution results of this Python application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 $ python3.10 main.py
{'name': 'leaf-01', 'os': 'cisco-nxos', 'ip': '192.168.1.1'}
Hostname: leaf-01
{'name': 'leaf-01', 'os': 'cisco-nxos', 'ip': '192.168.1.1'}
{'name': 'leaf-01', 'os': 'cisco-nxos', 'ip': '192.168.1.1', 'location': 'DC1'}
{'name': 'leaf-01', 'os': 'cisco-nxos', 'location': 'DC1'}
name: leaf-01
os: cisco-nxos
location: DC1
{'name': 'leaf-02', 'os': 'arista-eos', 'ip': '192.168.1.2'}
Hostname: leaf-02
{'name': 'leaf-02', 'os': 'arista-eos', 'ip': '192.168.1.2'}
{'name': 'leaf-02', 'os': 'arista-eos', 'ip': '192.168.1.2', 'location': 'DC1'}
{'name': 'leaf-02', 'os': 'arista-eos', 'location': 'DC1'}
name: leaf-02
os: arista-eos
location: DC1
{'name': 'spine-01', 'ip': '192.168.1.1'}
Hostname: spine-01
{'name': 'spine-01', 'ip': '192.168.1.1', 'os': None}
{'name': 'spine-01', 'ip': '192.168.1.1', 'os': None, 'location': 'DC1'}
{'name': 'spine-01', 'os': None, 'location': 'DC1'}
name: spine-01
os: None
location: DC1
[{'name': 'leaf-01', 'os': 'cisco-nxos', 'location': 'DC1'}, {'name': 'leaf-02', 'os': 'arista-eos', 'location': 'DC1'}, {'name': 'spine-01', 'os': None, 'location': 'DC1'}]
It is worth mentioning that modification of the dictionary whilst you loop over the higher-level list is propagated back to the original list. I’m sure you’ve noticed that in the snippet we have deleted one key and added another one. As a result, the original inventory was modified.
Go (Golang)
That was Python, now let’s convert it to Go (Golang) code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 /* From Python to Go: Python: 006 - Dictionaries and Maps */
package main
import (
"fmt"
)
func main() {
// Define inventory
inventory := []map[string]string{
{
"name": "leaf-01",
"os": "cisco-nxos",
"ip": "192.168.1.1",
}, {
"name": "leaf-02",
"os": "arista-eos",
"ip": "192.168.1.2",
}, {
"name": "spine-01",
"ip": "192.168.1.1",
},
}
// Loop through all network devices
for _, d := range inventory {
// Print the device data
fmt.Println(d)
// Print the hostname
fmt.Printf("Hostname: %v\n", d["name"])
// Add the OS key if it is missing
if _, ok := d["os"]; !ok {
d["os"] = ""
}
fmt.Println(d)
// Add new key-value pair
d["location"] = "DC1"
fmt.Println(d)
// Remove the IP key
delete(d, "ip")
fmt.Println(d)
// Go through all keys and values
for k, v := range d {
fmt.Printf("%v: %v\n", k, v)
}
}
fmt.Println(inventory)
}
Breakdown:
- As already mentioned earlier in this blog post, Go is strict typed. As such, you’d need to specify the type of our inventory, which we define as “[]map[string]string“, what means:
- “[]” is a slice, as discussed before.
- “map[string]string” of maps, which use string data type for keys and string data type for values.
- Accessing specific key within map, as well as changing its value or creating a new key is identical to Python and is achieved by calling “map[“key_name”]“.
- Checking if key exist in map is done via conditional if; however, the syntax is different to Python:
- 2 variables are return as the result of declaration: first value is variable, which you can put in special vaiable “_” meaning you aren’t interested in it, and the second variable is of boolean type returning true if key exists and false if it doesn’t. Commonly it is called “ok“.
- The conditional “!ok” means negation, so that the condition is true when “ok” is false. Essentially, “!” in Go (Golang) equals to Python “not” statement.
- To delete the the key from the map, function “delete()” is used, which takes 2 arguments: the name of the map and the name of the key to delete.
- In order to iterate over all key-value pairs, the same operator “range” is used, as for iterating over the list elements.
Execution of the Go (Golang) code provide the same result as Python’s one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 $ go run .
map[ip:192.168.1.1 name:leaf-01 os:cisco-nxos]
Hostname: leaf-01
map[ip:192.168.1.1 name:leaf-01 os:cisco-nxos]
map[ip:192.168.1.1 location:DC1 name:leaf-01 os:cisco-nxos]
map[location:DC1 name:leaf-01 os:cisco-nxos]
name: leaf-01
os: cisco-nxos
location: DC1
map[ip:192.168.1.2 name:leaf-02 os:arista-eos]
Hostname: leaf-02
map[ip:192.168.1.2 name:leaf-02 os:arista-eos]
map[ip:192.168.1.2 location:DC1 name:leaf-02 os:arista-eos]
map[location:DC1 name:leaf-02 os:arista-eos]
name: leaf-02
os: arista-eos
location: DC1
map[ip:192.168.1.1 name:spine-01]
Hostname: spine-01
map[ip:192.168.1.1 name:spine-01 os:]
map[ip:192.168.1.1 location:DC1 name:spine-01 os:]
map[location:DC1 name:spine-01 os:]
name: spine-01
os:
location: DC1
[map[location:DC1 name:leaf-01 os:cisco-nxos] map[location:DC1 name:leaf-02 os:arista-eos] map[location:DC1 name:spine-01 os:]]
Same remark as in Python is applicable here: whenever you change content of maps whilst looping through list, the changes are reflected in the original list.
Lessons in GitHub
You can find the final working versions of the files from this blog at out GitHub page.
Conclusion
By this point we have covered majority of data types, which you will face upon developing applications in Python and Go (Golang). I would say, we covered all “basic”, although it is not very scientific word. In the next blog post we will cover the last data type, which is of paramount importance in Go (Golang) and is also very important in Python. Continue labbing, continue studding. Take care and good bye.
Support us
P.S.
If you have further questions or you need help with your networks, I’m happy to assist you, just send me message. Also don’t forget to share the article on your social media, if you like it.
BR,
Anton Karneliuk