[go: up one dir, main page]

0% found this document useful (0 votes)
39 views168 pages

Python Automation Book Readthedocs Io en 1.0

Uploaded by

yemoeaung224kpu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
39 views168 pages

Python Automation Book Readthedocs Io en 1.0

Uploaded by

yemoeaung224kpu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 168

Python for Network Engineer's

Release 2.0

Syed Asif

Aug 16, 2024


TABLE OF CONTENTS:

1 What is Network Automation? 3


1.1 What is Computer Programming? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Understanding Python Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Python for Network Engineers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Installing Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Python Characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 dir() and help() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.7 What is Python pip? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.8 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.9 Installing Modules with pip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.10 Python Virtual Environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2 Basic Data Types 15


2.1 Variables in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2 Python Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3 Understanding Python Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4 Understanding Python Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5 Integers in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.6 Floats in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.7 Numbers - Other Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.8 Type Casting in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.9 Useful Built-in Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.10 Python Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

3 Collections and Sequences in Python 37


3.1 Understanding Lists in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 Understanding Tuples in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.3 What is a Tuple? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.4 Storing Different Data Types in a Tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.5 Using Parentheses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.6 Checking the Type of a Tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.7 Immutable Nature of Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.8 Accessing Elements in a Tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.9 Restrictions on Tuple Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.10 Tuple Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.11 Understanding Sets in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.12 How to Use Sets in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.13 Creating Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.14 Using Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.15 How to Modify Sets in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

i
3.16 How to Perform Set Operations in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.17 How Set Operations Can Help Network Engineers . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.18 Python Dictionaries: A Network Engineer’s Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.19 Similarities with Lists: Mutability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.20 Using Curly Braces to Create a Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.21 Navigating Dictionaries in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.22 Dictionary Toolbox: Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.23 Exploring Keys, Values, and Items . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.24 Dynamic Modifications with .pop() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.25 Deletion Strategies: del and update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.26 Dictionary Iteration Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.27 Nested Dictionaries in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.28 Understanding Mutable and Immutable Objects in Python . . . . . . . . . . . . . . . . . . . . . . . 55
3.29 Mutable Objects in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.30 How Lists Work Internally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.31 List Comprehensions: Simplifying Data Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.32 Advanced Techniques with Nested List Comprehensions . . . . . . . . . . . . . . . . . . . . . . . . 60
3.33 Using List Comprehensions for Data Transformation . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.34 Pros and Cons of List Comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.35 Set Comprehensions in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

4 Conditional Statements** 65
4.1 Understanding Booleans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.2 Boolean Logic in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3 Truthy and Falsy Values in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.4 None in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.5 Conditional Statements in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.6 Importance of Conditions in Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.7 Conditional Statements - elif and else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.8 Comparison Operators and Conditionals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.9 Logical Operators and Conditional Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.10 Nested Conditional Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.11 Truthy and Falsy Values in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.12 Idiomatic Expressions in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

5 Loops in Python 73
5.1 Understanding the For Loop in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2 Nesting Loops in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.3 While Loop in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.4 For vs. While Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

6 File Handling in Python 79


6.1 Working with Files in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

7 Exception Handling in Python for Network Engineers 83


7.1 Understanding Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.2 Handling Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.3 Handling Specific Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.4 Handling Multiple Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
7.5 Exceptions as Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

8 Functions and Classes 87


8.1 Functions and Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
8.2 Built-in Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
8.3 User-defined Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

ii
8.4 Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
8.5 Function Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
8.6 Positional Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
8.7 Default Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
8.8 Keyword (Named) Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.9 *args and **kwargs in Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.10 Handling Tuple and Dictionary as Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.11 Scope of Variables in function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.12 Classes and Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

9 Network Automation Lab 97


9.1 Setting Up a Network Lab with GNS3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
9.2 Prerequisites of Lab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
9.3 Starting the Lab Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
9.4 Adding Cloud Node to GNS3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
9.5 Installing VSCode and SSH Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
9.6 Connecting VSCode to Ubuntu Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
9.7 Finalizing the Lab Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.8 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

10 Telnet Programming in Python: Streamlining Network Operations 105


10.1 Understanding Telnet’s Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
10.2 Importance in Network Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
10.3 Integration with Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
10.4 Basics of Telnetlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.5 Security Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

11 Paramiko - Secure SSH Connections in Python 109


11.1 Setting up Paramiko for SSH Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
11.2 Establishing Secure Connections to Network Devices . . . . . . . . . . . . . . . . . . . . . . . . . . 109
11.3 Executing Commands and Handling Responses Asynchronously . . . . . . . . . . . . . . . . . . . . 110
11.4 Handling Exceptions and Error Scenarios Gracefully . . . . . . . . . . . . . . . . . . . . . . . . . . 111
11.5 Advanced Example: Creating Loopback Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
11.6 Connecting to Multiple Devices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
11.7 Connecting to Multiple Devices with a List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
11.8 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

12 Simplifying Network Device Management with Netmiko 119


12.1 What is Netmiko? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
12.2 Installing Netmiko . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
12.3 Connecting a Single Device . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
12.4 Simplify Device Connections with Python Dictionaries in Netmiko . . . . . . . . . . . . . . . . . . 120
12.5 Enabling Privilege EXEC Mode with Netmiko . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
12.6 Device Configuration with Netmiko . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
12.7 Safeguarding Passwords in Network Automation with Python’s getpass . . . . . . . . . . . . . . . . 123
12.8 Sending Multiple Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
12.9 Connecting to Multiple Devices with Netmiko . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
12.10 Simplifying Network Configuration with Netmiko . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
12.11 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

13 NAPALM - Unified Network Device Management 131


13.1 Overview of NAPALM and Its Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
13.2 Installation and Configuration Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
13.3 Working with Different Vendor Platforms using NAPALM . . . . . . . . . . . . . . . . . . . . . . . 132
13.4 Integrating NAPALM with Other Automation Tools and Frameworks . . . . . . . . . . . . . . . . . 135

iii
13.5 Configuration support Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

14 Getting Started with Nornir - Network Automation 137


14.1 Prerequisites and Key Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
14.2 Overview of Nornir Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
14.3 Configuring Nornir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
14.4 Writing and Running Your First Nornir Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
14.5 Integrating Netmiko with Nornir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
14.6 Integrating Python NAPALM with Nornir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

15 Appendix 149
15.1 Simplifying Network Automation with Python dotenv . . . . . . . . . . . . . . . . . . . . . . . . . 149
15.2 Understanding Python-dotenv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
15.3 Key Benefits of Python-dotenv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
15.4 Getting Started with Python-dotenv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
15.5 Using Python-dotenv in Your Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
15.6 Python Development: Essential VS Code Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
15.7 What is VS Code? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
15.8 What is a Virtual Environment? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
15.9 Essential Linux Networking Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
15.10 Essential Windows 10 Networking Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
15.11 1. ipconfig . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
15.12 2. netsh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
15.13 3. route . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
15.14 4. ping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
15.15 5. tracert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
15.16 6. netstat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
15.17 7. nslookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
15.18 8. getmac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
15.19 9. telnet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
15.20 10. pscp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
15.21 11. nmap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
15.22 12. arp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
15.23 13. net use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
15.24 14. gpupdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
15.25 15. systeminfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
15.26 16. tasklist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
15.27 17. taskkill . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
15.28 Using help Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
15.29 Additional Tips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

iv
Python for Network Engineer's, Release 2.0

Learn Network Programmability with Python, GNS3, and Cisco devices.


1. What you’ll learn
• Python fundamentals
• Network Automation with Python
2. About the author
• Associate Engineer (DAE in Electronics) exploring network automation, my skills as an Associate Engineer
include:
– Routing and Switching
– OFC/LAN Networking
– IP Addressing and Sub-netting
– Computer Basics - Windows 7/10
– Linux and Ubuntu Desktop/Server
– VMware/KVM/VirtualBox
– Docker/Vagrant - Hands On
– Ansible for Network Automation
– Python for Network Automation
3. Conventions
This book is a guide for network engineers and is intended for network engineers to write code. Therefore, no time
is spent on coding style. Programming concepts such as object-oriented programming are not covered in detail due
to their complexity. However, this book mainly focuses on getting Python scripting to work with minimal effort to
automate network devices.

. Warning

This book is under development.

TABLE OF CONTENTS: 1
Python for Network Engineer's, Release 2.0

2 TABLE OF CONTENTS:
CHAPTER

ONE

WHAT IS NETWORK AUTOMATION?

Network automation involves using technology and software to handle the configuration, management, and monitoring
of computer networks automatically. By automating these tasks, network engineers can save time and effort, manage
complex networks more efficiently, and reduce the risk of errors or misconfigurations. Automation also makes it easier
for organizations to scale their network infrastructure, as new devices can be provisioned and configured quickly and
consistently.
Here are some common examples of network automation:
• Configuration management: Automation tools ensure that network devices are configured consistently and
according to best practices, which helps prevent errors and improve security.
• Network monitoring: Automation tools monitor network performance and identify issues in real-time, allowing
network engineers to respond to problems more quickly and proactively.
• Provisioning: Automation tools can provision new network devices and configure them automatically, saving
time and reducing the risk of errors or misconfigurations.
• Security: Automation tools enforce security policies and configurations across the network, improving overall
security and compliance.
In summary, network automation is a powerful tool that helps organizations manage complex network environments
more efficiently and effectively.

1.1 What is Computer Programming?

Every computer operates based on a set of instructions known as a computer program. These programs are essential as
they transform computer hardware into a functional device. Think of a computer as a piano; without a skilled musician,
it remains silent.
Computers excel at performing basic operations like addition and division at incredible speeds and with perfect accu-
racy. However, they don’t intuitively understand concepts like humans do. For example, if you want to calculate your
average speed on a road trip, you need to provide the computer with precise instructions:
1. Input the distance traveled.
2. Input the travel time.
3. Calculate the average speed by dividing the distance by the time.
4. Display the result in a human-readable format.
These steps, though simple, form a computer program. While these actions differ from what a computer naturally
understands, they can be translated into a language that the computer can process.

3
Python for Network Engineer's, Release 2.0

1.1.1 Natural Languages and Programming Languages

Human languages help us express our thoughts and share knowledge. Some languages use gestures or body language,
while others, like our native languages, use words to convey our ideas. Computers have their own language called
machine language, which is very complex and hard for humans to understand.
Even the most advanced computers aren’t truly intelligent. They only follow a set of basic commands, like “take this
number, divide it by another, and save the result.” This set of commands is called an instruction list (IL).

1.1.2 The Anatomy of a Language

Every language, whether it’s a natural language or a machine language, has the following key elements:
• Alphabet: A set of symbols used to create words.
• Lexis (Dictionary): A collection of words and their meanings.
• Syntax: Rules that govern how sentences and phrases are structured.
• Semantics: Rules that determine the meaning of sentences and phrases.
In machine language, the IL serves as the alphabet (using zeros and ones). However, humans need a more expressive
language to write programs that computers can execute. These high-level programming languages are similar to nat-
ural languages. They have symbols, words, and rules that humans can understand, allowing us to give commands to
computers.
A program written in a high-level programming language is called source code, and the file containing this source
code is known as a source file.

1.1.3 Compilation vs. Interpretation

Computer programming involves using elements of a chosen programming language to achieve a desired outcome.
This outcome depends on the programmer’s imagination, knowledge, and experience.
There are two main methods for translating a program from a high-level programming language into machine language:
1. Compilation: The source program is translated once to create a file containing machine code. This file can be
distributed globally, and the program responsible for this translation is called a compiler.
2. Interpretation: The source program is translated each time it needs to run. The program performing this trans-
formation is an interpreter, which interprets the code every time it’s executed. This means you can’t distribute
the source code as-is; the end-user also needs the interpreter to run it.

Compilation — Advantages and Disadvantages

• Advantages:
– Executed code is typically faster.
– Only the user needs the compiler; the end-user can use the code without it.
– The translated code is stored in machine language, keeping it secure.
• Disadvantages:
– Compilation is time-consuming; you can’t run your code immediately after making changes.
– You need a compiler for each hardware platform you want your code to run on.

4 Chapter 1. What is Network Automation?


Python for Network Engineer's, Release 2.0

Interpretation — Advantages and Disadvantages

• Advantages:
– You can run the code as soon as you finish writing it; no need for additional translation phases.
– The code is stored in a programming language, not machine language, so it can run on different architectures
without separate compilation.
• Disadvantages:
– Interpretation doesn’t result in high-speed execution; your code shares resources with the interpreter.
– Both you and the end-user need the interpreter to run your code.
Python is an interpreted language. To program in Python, you need a Python interpreter. Without it, you can’t exe-
cute your code. The best part is that Python is free, which is one of its biggest advantages. Languages designed for
interpretation are often called scripting languages, and the source programs written in them are called scripts.

1.2 Understanding Python Language

Python is a popular, interpreted, object-oriented, high-level programming language with dynamic semantics. It’s used
for general-purpose programming and is known for its versatility. The name “Python” comes from an old BBC televi-
sion comedy sketch series called Monty Python’s Flying Circus.
Python was created by Guido van Rossum, who was born in 1956 in Haarlem, the Netherlands. Python’s popularity
has grown worldwide, but it all started with Guido’s vision.
In 1999, Guido van Rossum outlined his goals for Python:
• Create an easy, intuitive, and powerful language.
• Keep it open source.
• Make it understandable, like plain English.
• Ensure it’s suitable for everyday tasks, allowing for short development times.
Python has matured and gained trust in the programming world. It’s not just a passing trend but a significant player in
the programming landscape.

1.2.1 What Sets Python Apart?

There are many reasons why Python stands out:


• Easy to Learn: Python takes less time to learn compared to many other languages, allowing you to start pro-
gramming quickly.
• Easy to Teach: Teaching Python is straightforward, focusing on programming techniques rather than complex
language intricacies.
• Easy to Use: Python often lets you write code faster when creating new software.
• Easy to Understand: Python code is generally easier to read and maintain.
• Easy to Obtain, Install, and Deploy: Python is free, open-source, and cross-platform, making it accessible to
everyone.

1.2. Understanding Python Language 5


Python for Network Engineer's, Release 2.0

1.2.2 Python’s Competitors

Python has two direct competitors with similar properties and capabilities:
• Perl: A scripting language that leans towards tradition and convention, with similarities to older languages
derived from classic C programming.
• Ruby: Another scripting language that is more progressive and filled with fresh ideas. Python finds its place
somewhere between these two options.
Python’s growth is evident as more development tools are implemented in Python. Many everyday applications are
being written in Python, and numerous scientists have switched from expensive proprietary tools to Python.

1.3 Python for Network Engineers

Python is a versatile and powerful programming language that has become essential for network engineers. Whether
you’re new to networking or an experienced professional, Python offers numerous benefits for network-related tasks.
Let’s explore the key aspects of Python for network engineering.

1.3.1 Network Automation

Python helps network engineers automate repetitive tasks such as making configuration changes, creating backups, and
monitoring networks.

1.3.2 Configuration Management

Python-based tools like Ansible, NAPALM, and Netmiko are widely used for managing network configurations.

1.3.3 Monitoring and Troubleshooting

Python allows network engineers to create custom tools for monitoring and troubleshooting, ensuring the continuous
health of a network.

1.3.4 Network Security

Python plays a crucial role in implementing strong security policies, analyzing network traffic, and responding to
security incidents.

1.3.5 Cross-Platform Compatibility

Networking involves various operating systems and devices. Python’s cross-platform compatibility means that code
written in Python can run on different operating systems without significant changes.
In summary, Python empowers network engineers to streamline operations, improve network efficiency, and enhance
security. Whether you’re managing a small network or a large infrastructure, Python equips you to handle network
automation tasks with greater efficiency and effectiveness.

6 Chapter 1. What is Network Automation?


Python for Network Engineer's, Release 2.0

1.4 Installing Python

Before you start using Python for network automation, it’s important to know how to set it up on your operating system.
Let’s go through the steps for different systems.

1.4.1 On Windows

Python might not be pre-installed on Windows, but installing it is straightforward:


1. Visit the official Python website and download the Windows installer for your desired Python version.
2. Run the installer and follow the instructions. Make sure to select the option to “Add Python to PATH” during
installation.

1.4.2 On macOS

Python is often pre-installed on macOS, but you might want to manage your own installation:
1. Download the Python installer for macOS from the official website.
2. Run the installer and follow the instructions.
3. Although macOS usually comes with Python 2.7, it’s recommended to install the latest Python 3 version for
compatibility with newer packages.

1.4.3 On Linux

Linux distributions typically include Python, but you might need to install specific packages:
• For Debian/Ubuntu-based systems, use apt to install Python:

sudo apt update


sudo apt install python3

• For Red Hat/Fedora-based systems, use dnf or yum:

sudo dnf install python3


sudo yum install python3

For other Linux distributions, check your system’s package manager for the appropriate commands.

1.4.4 Python Interpreter

The Python interpreter allows you to run Python scripts and execute code interactively. It’s a great tool for learning and
developing in Python. Here’s how to use it:
1. Open your terminal or command prompt.
2. Type python or python3 and press Enter. You should see the Python interpreter prompt (>>>), indicating you’re
in interactive mode.
Now, you can enter Python code directly, and the interpreter will execute it. For example, try entering print("Hello,
Python!"), and you’ll see the output immediately.

1.4. Installing Python 7


Python for Network Engineer's, Release 2.0

Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32

Type "help", "copyright", "credits" or "license" for more information.


>>> print("Hello, Python!")
Hello, Python!

The Python interpreter is excellent for testing small pieces of code, experimenting with Python features, and quickly
seeing the results. Let’s start by creating a variable called host_name and assigning it a value:

>>> host_name = 'router'

As you can see, there’s no need to declare the variable type first. This is why Python is called a dynamic language,
unlike some programming languages like C and Java. Now, you can print the variable:

>>> host_name = 'router'


>>> print(host_name)
router
>>> host_name
'router'

Once a variable is assigned, you can easily print it using the print() command. In the Python shell, you can also print
the value of hostname or any other variable by just typing the variable name and pressing Enter. This is particularly
helpful when you’re learning Python or troubleshooting your scripts.

1.4.5 Print Function

The print() function is a fundamental tool for displaying output in Python. It allows you to communicate information
to users, debug your code, and provide feedback. To use the print() function, follow this format:

>>> print("Hello, Python!")


Hello, Python!

Here, the text enclosed in double quotes is the message you want to display. You can print variables, numbers, or any
other data type using the print() function.

1.4.6 Input Function

The input() function is equally important. It enables your Python programs to interact with users by accepting input
from them. The input() function presents a prompt to the user, and the user’s input is returned as a string. Here’s an
example of using the input() function:

>>> user_input = input("Please enter your name: ")


Please enter your name: John
>>> print("Hello, " + user_input + "!")
Hello, John!

In this example, the input() function displays the message “Please enter your name: “ and waits for the user to type
their name. The user’s input is then stored in the user_input variable.

8 Chapter 1. What is Network Automation?


Python for Network Engineer's, Release 2.0

1.5 Python Characteristics

Understanding key characteristics of Python can help you write cleaner code:

1.5.1 Indentation

Indentation is a fundamental aspect of Python’s syntax. Unlike many programming languages that use curly braces {}
to define code blocks, Python relies on indentation. Proper indentation ensures that your code is structured correctly
and is a crucial aspect of Python’s readability.

1.5.2 Use of Spaces

Consistent use of spaces is essential in Python, particularly for indentation. The recommended standard, according
to the PEP8 style guide, is to use four spaces for each level of indentation. Adhering to this standard enhances code
readability and maintainability.

1.5.3 Python Script and Execution

To create and execute a Python script, follow these steps:


• Create a Python script file with a .py extension, for instance, my_code.py.
• In Linux or macOS, you can include a “shebang” line at the beginning of your script to specify the Python
interpreter to use:

#!/usr/bin/env python

This line tells the system to use the Python interpreter located at /usr/bin/env python. It ensures that your script
runs with the correct Python version.
• If needed, adjust the script’s permissions to make it executable. You can use the chmod command on Unix-based
systems:

chmod +x my_code.py

This command makes the script executable.


• On Windows, run the script using the following command:

python my_code.py

Alternatively, you can use the Python launcher with the py command:

py my_code.py

Here’s an example script:

#!/usr/bin/env python
ip_addr = input("Enter an IP Address: ")
print("You entered:", ip_addr)

By following these steps, you can create, execute, and manage Python scripts efficiently.

1.5. Python Characteristics 9


Python for Network Engineer's, Release 2.0

1.5.4 Comments in Code

Comments play a pivotal role in documenting your code and assisting both yourself and others in understanding the
purpose of different parts of your script. Python supports single-line comments that begin with the # symbol. You can
also include inline comments for additional context:

>>> # This is a comment


>>> ip_addr = "10.1.1.1"
>>> # This line prints the IP address
>>> print(ip_addr)
10.1.1.1

For multi-line comments, Python allows the use of triple-quotes (''') or (""") at the beginning and end of the comment
block.

1.6 dir() and help()

Python provides useful tools like the dir() and help() functions, which are invaluable for exploring and understand-
ing Python libraries and modules, especially in network engineering tasks.

1.6.1 dir() Function

The dir() function lists the attributes and methods of objects, giving you insights into what an object or module can
do. For example, to explore the functionality of the os module, you can use dir(os):

Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32

Type "help", "copyright", "credits" or "license" for more information.


>>> import os
>>> dir(os)
# Output is omitted
['readlink', 'remove', ..... 'walk', 'write']

1.6.2 help() Function

The help() function provides detailed information about specific functions or modules. You can use it to get docu-
mentation and usage examples. For instance, typing help(os) will give you information about the os module.
To learn how to use a method listed by dir(), you can use the help() function. Here’s an example of using help()
to understand the upper method:

>>> dir(hostname)
# Output is omitted
['partition', .... 'upper', 'zfill']
>>> help(hostname.upper)
Help on built-in function upper:

upper() method of builtins.str instance


Return a copy of the string converted to uppercase.

10 Chapter 1. What is Network Automation?


Python for Network Engineer's, Release 2.0

1.6.3 Recommended Flow

1. Check your data type using type().


2. Check available methods for your object using dir().
3. Learn how to use a method by using help().
These tools can be used on any Python object, not just strings.
Python is an indispensable tool for network engineers, offering automation capabilities for tasks like configuration
management, monitoring, and security. It facilitates rapid prototyping, cross-platform compatibility, and data analysis,
making it a versatile asset in managing networks of all sizes.

1.7 What is Python pip?

pip is the package installer for Python. You can use pip to install packages from the Python Package Index and other
repositories.

1.8 Installation

Usually, pip is automatically installed if you are:


• Working in a virtual environment
• Using Python downloaded from python.org
• Using Python that has not been modified by a redistributor to remove ensurepip

1.8.1 Supported Methods

If your Python environment does not have pip installed, there are two mechanisms supported directly by pip’s main-
tainers:
• ensurepip: Python comes with an ensurepip module that can install pip in a Python environment.
• get-pip.py: This is a Python script that uses some bootstrapping logic to install pip. You can download the
script from the official site and run it.

1.8.2 Alternative Methods

Depending on how you installed Python, there might be other mechanisms available for installing pip, such as using
Linux package managers.

1.7. What is Python pip? 11


Python for Network Engineer's, Release 2.0

Debian/Ubuntu

On Ubuntu, pip often comes pre-installed. If not, you can install it with the following commands:

sudo apt update


sudo apt install python3-venv python3-pip

To check installed modules via pip, use pip list, and to check the pip version, use pip --version.

1.9 Installing Modules with pip

Python has an active community of contributors and users who make their software available for others to use under
open-source license terms. pip is the preferred installer program and is included by default with Python binary installers
starting from Python 3.4.

1.9.1 Basic Usage

The following command will install the latest version of a module and its dependencies from the Python Packaging
Index:

python3 -m pip install netmiko

You can also specify an exact or minimum version directly on the command line:

python -m pip install SomePackage==1.0.4 # specific version

1.9.2 Upgrading Existing Modules

To upgrade existing modules, use:

python3 -m pip install --upgrade netmiko

To upgrade pip itself, use:

python3 -m pip install -U pip

These steps should help you get started with pip and managing Python packages effectively.

1.10 Python Virtual Environments

A Python virtual environment allows you to install third-party packages for testing without affecting your system’s
Python installation. Here are two common methods to create Python virtual environments:
• The built-in venv module
• The virtualenv package

12 Chapter 1. What is Network Automation?


Python for Network Engineer's, Release 2.0

1.10.1 Python’s venv Library

To use the venv module, you can run Python with the -m flag, which tells Python to run the specified module. Open a
command prompt on Windows or a terminal on macOS or Linux, and type the following:

python -m venv test

This command creates a folder named test in your current directory. To activate the virtual environment, navigate to
the test folder and run this command on Linux/macOS:

source bin/activate

You can now install new packages in your virtual environment without affecting your system. When you’re done,
deactivate the virtual environment by running the deactivate command.

1.10.2 The virtualenv Package

The virtualenv package was the original method for creating Python virtual environments. It has some advantages
over the venv module:
• It’s faster.
• It can create virtual environments for multiple Python versions.
• It can be upgraded via pip.
To install virtualenv, use pip:

pip install virtualenv

Once installed, create a virtual environment with this command:

virtualenv <FOLDER_NAME>

Virtual Environment for a Specific Version

To create a virtual environment for a specific Python version, use:

virtualenv -p python3.7 venv

This command won’t work with the venv module. Activating, deactivating, and freezing work the same way as with
the venv module.

1.10.3 Pin Your Dependencies

To make your virtual environments reproducible, you can create a requirements.txt file while your virtual environ-
ment is active:

python -m pip freeze > requirements.txt

After working or deleting your venv folder, recreate the same environment with the requirements.txt file:

1.10. Python Virtual Environments 13


Python for Network Engineer's, Release 2.0

virtualenv new-venv
source new-venv/bin/activate
python -m pip install -r requirements.txt

For more information, check out these resources:


• Python Virtual Environments: A Primer
• Set Up a Local Programming Environment on Ubuntu 20.04

14 Chapter 1. What is Network Automation?


CHAPTER

TWO

BASIC DATA TYPES

This chapter covers the basics of Python, which are essential for writing programs or scripts. Python has various data
types like sets, strings, lists, and dictionaries that are important for programming. It also includes numeric data types
and strings, which are sequences of characters.
• Lists: Ordered sequences of elements that can be changed (mutable).
• Sets: Collections of unordered elements that cannot be changed (immutable).
• Tuples: Similar to sets but use parentheses and are also immutable.
These elements help programmers create effective Python programs for various applications.
Python variables can store different types of data, and each type can perform different functions. Here are the built-in
data types in Python:
• Sequences:
1. String: str()
2. List: list()
3. Tuple: tuple()
4. Range: range()
• Mapping:
1. Dictionary: dict()
• Set: set()
• Numbers:
1. Integer: int()
– Boolean Value: bool()
2. Float: float()
3. Complex: Complex numbers
• None: Represents the absence of a value

15
Python for Network Engineer's, Release 2.0

2.1 Variables in Python

A Python variable is like a container that stores data temporarily. You can use this stored data in your code. Variables
are essential in any programming language.
• Variables keep values accessible.
• Variables give values meaning.
• Variables make changes easy.
• Variables have data types.

2.1.1 Variable Declaration

Python is a dynamically typed language, meaning you create a variable by assigning a value to it. You don’t need to
declare its type, and you can change its type later.

Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32

Type "help", "copyright", "credits" or "license" for more information.


>>> # Assign a string value to a variable
>>> device_name = "Router1"
>>> # Assign an integer value to a variable
>>> device_port = 22
>>> # Assign a boolean value to a variable
>>> is_connected = True

You can find out the data type of a variable using the type() function.

>>> ip_addr = "192.168.10.1"


>>> print(type(ip_addr))
<class 'str'>

You can assign values to multiple variables in one line.

>>> vlan_01, vlan_10 = "default", "mgmt"


>>> print(vlan_01, vlan_10)
default mgmt

You can also assign the same value to multiple variables at once.

>>> host = ip_addr = "192.168.10.1"


>>> print(host, ip_addr)
192.168.10.1 192.168.10.1

If you have a list of values, you can extract them into variables.

>>> ip_addr_list = ["10.10.10.10", "172.16.10.10", "192.168.10.10"]


>>> ip_addr1, ip_addr2, ip_addr3 = ip_addr_list
>>> print(ip_addr1)
10.10.10.10
>>> print(ip_addr2)
172.16.10.10
(continues on next page)

16 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

(continued from previous page)


>>> print(ip_addr3)
192.168.10.10

2.1.2 Assignment Statements

An operator is a symbol that performs operations on values. The assignment operator = assigns the value on the right
to the variable on the left.

>>> hostName = "R-01"


>>> print(hostName)
R-01

You can use single or double quotes for string variables.

>>> hostName = "R-01"


>>> print(hostName)
R-01
>>> hostName = 'R-01'
>>> print(hostName)
R-01

2.1.3 Variable Naming Convention

Variable names can be long or short, but there are some rules:
• Must start with a letter or underscore.
• Cannot start with a number.
• Can only contain letters, numbers, and underscores.
• Keywords cannot be used as variable names.
• Variable names are case-sensitive.

>>> # Camel Case - Each word, except the first, starts with a capital letter.
>>> myVariableName = "Alex"
>>> # Pascal Case - Each word starts with a capital letter.
>>> MyVariableName = "Alex"
>>> # Snake Case - Words are separated by underscores.
>>> my_variable_name = "Alex"

Use all-caps for constants or configuration values that shouldn’t change.

>>> # Define a constant value using all-caps naming convention


>>> MAX_DEVICES = 100

Using meaningful variable names like device_name, device_ip, device_username, and device_password makes
your code more readable and easier to understand.

2.1. Variables in Python 17


Python for Network Engineer's, Release 2.0

2.1.4 Use Descriptive Names

Choose variable names that are descriptive and convey their purpose. Avoid generic names like temp or data. Instead,
use names like ip_address or server_name to make your code more readable.

2.1.5 Use Underscores

For multi-word variable names, use underscores to separate words, following the snake_case convention. For example,
device_name is more readable than deviceName.

2.1.6 Avoid Reserved Words

Be careful not to use Python’s reserved words as variable names. For example, naming a variable print or for can
lead to unexpected behavior.

2.1.7 Consistency

Maintain consistency in your variable naming. If you use ip_address in one part of your code, don’t switch to
ip_addr elsewhere. Consistency simplifies code comprehension.

2.2 Python Naming Conventions

Following naming conventions is essential for writing clean and maintainable Python code. Here are some common
conventions:
• snake_case_lower for variables and functions.
• PascalCase for class names.
• SNAKE_CASE_UPPER for constants.
Let’s dive deeper into these conventions:

2.2.1 Variable Names

• Start variable names with a lowercase letter or underscore.


• Use clear and descriptive names that convey the variable’s purpose.
• For multi-word variable names, use underscores for separation (e.g., user_id).

2.2.2 Function Names

• Begin function names with a lowercase letter or underscore.


• Use descriptive names that hint at the function’s action or purpose.
• For multi-word function names, use underscores (e.g., calculate_speed).

18 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

2.2.3 Class Names

• Start class names with an uppercase letter.


• Use CamelCase, where each word in the name begins with an uppercase letter and has no underscores (e.g.,
NetworkDevice).

2.2.4 Constant Names

• Constant variables should be in uppercase with words separated by underscores (e.g., MAX_CONNECTIONS).
By following these naming conventions, you’ll make your Python code more accessible and comprehensible to yourself
and others who collaborate on your projects.

2.3 Understanding Python Strings

Python is a versatile programming language with various data types, including strings, numbers, lists, and dictionaries.
In this section, we’ll focus on strings.
A string in Python is a sequence of characters (letters, numbers, symbols) enclosed in single or double quotation marks.
Strings are essential for tasks like text processing, data manipulation, and user interactions. You can combine, slice,
modify, and process strings in many ways, making them crucial in any Python program.

2.3.1 Creating Strings

In Python, you can create strings using single or double quotes. For example:

Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32

Type "help", "copyright", "credits" or "license" for more information.


>>> my_var = 'Switch-A'
>>> print(my_var)
Switch-A

Python allows you to use both single and double quotes, which is helpful when you need to include one type of quote
within the string. For example:

>>> mixed_quotes = "It's a great day to learn Python with 'strings'!"


>>> print(mixed_quotes)
It's a great day to learn Python with 'strings'!

In this example, we used both single and double quotes to create the mixed_quotes string.
You can check the data type of a variable using the type() function. For example:

>>> my_var = 'Switch-A'


>>> print(type(my_var))
<class 'str'>

This will return <class 'str'>, indicating that my_var is a string.


Python also supports multiline strings, which are useful for text that spans multiple lines. You can create multiline
strings using triple quotes:

2.3. Understanding Python Strings 19


Python for Network Engineer's, Release 2.0

>>> multi_line = '''This is the first line,


... This is the second line.'''
>>> print(multi_line)
This is the first line,
This is the second line.

However, you must start and end a string with the same type of quote. Mixing single and double quotes will result in a
syntax error.

2.3.2 String Methods

In Python, a method is a function associated with an object that lets you perform specific actions on that object. For
strings, there are many methods to manipulate and work with text data.
When you use a string method, you need to include parentheses () after the method name. Without them, the method
won’t run.
String methods create a new string and leave the original string unchanged. To keep the result, you need to assign it to
a new variable or overwrite the original one. For example:

>>> my_var = "some string"


>>> my_var = my_var.upper()
>>> print(my_var)
SOME STRING

In this code, the upper() method creates a new string with all uppercase characters, and this new string is assigned
back to my_var. The original value of my_var remains unchanged.

split() Method

The .split() method breaks down strings into smaller parts. By default, it splits the string at spaces, turning a
sentence into individual words and returning them as a list. For example:

>>> sentence = "This is a sentence that says something very useful"


>>> words = sentence.split()
>>> print(words)
['This', 'is', 'a', 'sentence', 'that', 'says', 'something', 'very', 'useful']

This is useful for tokenizing text.


You can also customize the .split() method by specifying a separator. For example, to split an IP address:

>>> ip_addr = "172.31.21.15"


>>> components = ip_addr.split(".")
>>> print(components)
['172', '31', '21', '15']

The .splitlines() method splits a string into separate lines based on line breaks. This is useful for multiline text
data.

>>> multiline_text = "The first line.\nSecond line.\nAnd third line."


>>> lines = multiline_text.splitlines()
>>> print(lines)
['The first line.', 'Second line.', 'And third line.']

20 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

This is helpful when working with multi-line text, such as reading content from files.

.join() Method

The .join() method in Python is the opposite of the .split() method. It lets you merge a list of strings into one
string. This is useful for creating strings with custom separators. For example, you can take a list like ['172', '31',
'21', '15'] and join them with periods to make an IP address:

>>> octets = ['172', '31', '21', '15']


>>> ip_address = ".".join(octets)
>>> print(ip_address)
172.31.21.15

You can also use other separators, like hyphens:

>>> octets = ['172', '31', '21', '15']


>>> formatted_address = "-".join(octets)
>>> print(formatted_address)
172-31-21-15

The .join() method is flexible and useful for various string tasks.

.strip() Method

The .strip() method removes leading and trailing whitespace from a string. This includes spaces, tabs, and newline
characters. For example:

>>> sentence = " In this we have leading and trailing whitespace. "
>>> cleaned_sentence = sentence.strip()
>>> print(cleaned_sentence)
In this we have leading and trailing whitespace.

The result is a string without the extra spaces.


These methods don’t change the original string; they create a new, cleaned version. You can also use .rstrip() to
remove trailing whitespace and .lstrip() to remove leading whitespace. These methods are helpful for cleaning
data, validating input, and normalizing text in Python applications.

Searching Substrings

Searching for specific keywords or patterns in configuration files is essential. The find() method helps you locate
substrings within a string and determine their positions.

>>> config = "hostname R1\nip address 192.168.1.1"


>>> position = config.find("ip address") # index of "ip address"
>>> print(position)
12

This code finds the position of “ip address” in the string config.
Commonly used string methods in network scripting include upper(), split(), and find().

2.3. Understanding Python Strings 21


Python for Network Engineer's, Release 2.0

startswith() and endswith() Methods

• startswith(): Checks if a string starts with certain characters.


• endswith(): Checks if a string ends with certain characters.

>>> ipaddr = '10.100.20.5'


>>> print(ipaddr.startswith('10')) # True
True
>>> print(ipaddr.endswith('5')) # True
True

Both methods return True if the string matches the specified characters; otherwise, they return False.

Chaining Methods

In Python, you can chain string methods together for concise and efficient string manipulation. This involves applying
multiple methods sequentially to a string.

>>> my_var = " Some String "


>>> my_var = my_var.lower().strip()
>>> print(my_var)
some string

Here, .lower() converts the string to lowercase, and .strip() removes leading and trailing whitespace. This re-
sults in a cleaned and transformed string in one line of code. Chaining methods makes your code more readable and
streamlines string manipulation.

2.3.3 String Formatting

Python offers different ways to format strings. Let’s look at an older method using the % operator.

Basic Formatting

The % operator inserts values into a string using placeholders. For example:

>>> print("My name is: %s" % "John")


My name is: John

Here, %s is a placeholder for a string, and "John" is inserted in its place.

Using Tuples

The % operator can also work with tuples to insert multiple values into a string:

>>> name = "John"


>>> age = 25
>>> print("My name is %s and I'm %d years old." % (name, age))
My name is John and I'm 25 years old.

In this example, %s is for a string and %d is for an integer. The values (name, age) replace these placeholders.
While this method works, it’s older and less flexible than newer methods like f-strings and the .format() method.

22 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

The .format() Method

The .format() method is more modern and versatile. It replaces placeholders with values inside {}. For example:

>>> name = "John"


>>> age = 25
>>> print("My name is {} and I'm {} years old.".format(name, age))
My name is John and I'm 25 years old.

With .format(), you can insert values in any order, repeat them, or format them in various ways.

String Literals (f-strings)

Introduced in Python 3.6, f-strings offer a concise and readable way to format strings. You place an f or F before the
string and embed expressions directly within curly braces {}:

>>> name = "John"


>>> age = 25
>>> print(f"My name is {name} and I'm {age} years old.")
My name is John and I'm 25 years old.

F-strings are great for their simplicity and clarity. They evaluate expressions and insert their results directly into the
string, making complex formatting tasks easy.

2.3.4 Additional Aspects of f-strings

F-strings in Python offer powerful features for advanced string formatting:


Allowing Expressions: You can include expressions within the curly braces {}. For example:

>>> print(f"Expressions: {2 + 7}")


Expressions: 9

This will output: Expressions: 9


Extracting Elements from a String: You can use f-strings to extract and display specific elements from strings. For
example, to get the first part of an IP address:

>>> ip_addr = "172.31.21.15"


>>> print(f"Print 1st element: {ip_addr.split('.')[0]}")
Print 1st element: 172

This will output: Print 1st element: 172

2.3. Understanding Python Strings 23


Python for Network Engineer's, Release 2.0

2.3.5 Creating Columns

You can format text into columns with f-strings by specifying a column width and alignment. This is particularly useful
for creating neatly aligned output.
• Default Left Alignment:

>>> ip_addr1 = "172.31.21.15"


>>> ip_addr2 = "192.168.10.1"
>>> ip_addr3 = "10.10.10.1"
>>> print(f"{ip_addr1:20}{ip_addr2:20}{ip_addr3:20}")
172.31.21.15 192.168.10.1 10.10.10.1

Here, each IP address is printed with a width of 20 characters, aligned to the left by default.
• Right Alignment (>):

print(f"{ip_addr1:>20}{ip_addr2:>20}{ip_addr3:>20}")
172.31.21.15 192.168.10.1 10.10.10.1

By using >, the IP addresses are right-aligned within their 20-character width.
• Center Alignment (^):

print(f"{ip_addr1:^20}{ip_addr2:^20}{ip_addr3:^20}")
172.31.21.15 192.168.10.1 10.10.10.1

Using ^, the IP addresses are centered within their 20-character width.

2.3.6 Formatting Floats

You can control the number of decimal places for floating-point numbers using f-strings.

>>> my_var = 1 / 3
>>> print(f"My Var: {my_var:.2f}")
My Var: 0.33

In this example, my_var is formatted to two decimal places.

2.3.7 Date Formatting

You can format dates using f-strings to display them in a more readable format.

>>> from datetime import datetime


>>> now = datetime.now()
>>> print(f"Date: {now:%B %d, %Y}")
Date: August 15, 2024

Here, the current date is formatted to show the full month name, day, and year.
These features make f-strings a valuable tool for precise and flexible string formatting in Python, allowing you to create
well-structured and readable output easily.

24 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

2.3.8 Other Characteristics of Strings

Strings in Python have several important characteristics that make them versatile and fundamental. Let’s explore some
of these:

Checking String Membership

You can check if a specific substring exists within a string using the in operator:

>>> text = "This is a sample text."


>>> if "sample" in text:
... print("Found 'sample' in the text.")
...
Found 'sample' in the text.

Raw Strings

You can create raw strings using the r or R prefix, which treats backslashes as literal characters. This is useful for
regular expressions and file paths:

>>> raw_string = r"C:\Users\Username\Documents"


>>> print(raw_string)
C:\Users\Username\Documents

String Concatenation

You can combine strings using the + operator:

>>> first_name = "John"


>>> last_name = "Doe"
>>> full_name = first_name + " " + last_name
>>> print(full_name)
John Doe

Strings as Sequences

Strings are sequences of characters, meaning they have a defined order, and you can access their elements by index.

Indexing from Left

Strings are indexed from left to right, starting at 0 for the first character:

>>> text = "Hello"


>>> first_character = text[0] # Accessing the first character
>>> print(first_character)
H

2.3. Understanding Python Strings 25


Python for Network Engineer's, Release 2.0

String Length and Loop

You can find the length of a string using the len() function and loop over the characters of a string with a for loop:

>>> text = "Python"


>>> length = len(text) # Get the length of the string
>>> print(f"The string has {length} characters.")
The string has 6 characters.
>>> for char in text:
... print(char)
...
P
y
t
h
o
n

Understanding these characteristics and behaviors of strings is fundamental for working with text data in Python.

2.4 Understanding Python Numbers

In Python, numbers are a fundamental data type used for various calculations. There are two main types of numbers:
integers (int) and floating-point numbers (float).
1. Integers (int): These are whole numbers, which can be positive, negative, or zero. Examples include 5, -10, and
0. Integers are used for countable objects or discrete values.
2. Floating-Point Numbers (float): These numbers have a decimal point or are written in scientific notation, like
3.14 or 2.5e-3. Floats are used for real numbers, including fractions and approximate calculations.
These two types are essential for handling a wide range of mathematical operations in Python.

2.5 Integers in Python

Python provides various tools for working with integers, including creating them, checking their type, and performing
standard math operations. Here’s how you can use these features:

2.5.1 Creating an Integer

To create an integer variable, just assign a whole number to it:

Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32

Type "help", "copyright", "credits" or "license" for more information.


>>> my_var = 22
>>> print(my_var)
22

In this example, my_var is assigned the integer value 22.

26 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

2.5.2 Checking the Type of an Integer

You can check the type of a variable using the type() function:

>>> my_var = 22
>>> print(type(my_var))
<class 'int'>

This confirms that my_var is of type int.

2.5.3 Math Operations with Integers

Python allows you to perform basic math operations on integers:


• Addition: Use the + operator:

>>> result = 17 + 22
>>> print(result)
39

• Subtraction: Use the - operator:

>>> result = 22 - 7
>>> print(result)
Output: 15

• Multiplication: Use the * operator:

>>> result = 3 * 4
>>> print(result)
Output: 12

• Division: Use the / operator:

>>> result = 4 / 7
>>> print(result)
0.5714285714285714

These basic operations are essential for working with integers, making Python a powerful tool for various mathematical
computations and data manipulation tasks.

2.6 Floats in Python

Working with floating-point numbers (floats) in Python is straightforward. Here’s how you can create, check the type,
and perform basic math operations with floats:

2.6. Floats in Python 27


Python for Network Engineer's, Release 2.0

2.6.1 Creating a Float

To create a float variable, just assign a number with a decimal point to it:

>>> my_var = 3.3


>>> print(my_var)
3.3

In this example, my_var is assigned the float value 3.3.

2.6.2 Checking the Type of a Float

You can check the type of a variable using the type() function:

>>> my_var = 3.3


>>> print(type(my_var))
<class 'float'>

This confirms that my_var is a float.

2.6.3 Math Operations with Floats

Python allows you to perform basic math operations on floats:


• Addition: Use the + operator:

>>> result = 3.3 + 2.2


>>> print(result)
5.5

• Division: Use the / operator:

>>> result = 7 / 2
>>> print(result)
3.5

• Multiplication: Use the * operator:

>>> result = 3.1 * 2.5


>>> print(result)
7.75

2.6.4 Rounding Numbers

You can round float numbers using the round() function. For example, to round the result of 4 divided by 3 to the
nearest integer:

>>> result = round(4 / 3)


>>> print(result)
1

28 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

The result variable now holds the integer value 1, which is the result of rounding 4/3.
Floats are essential for handling real numbers and approximate calculations, making Python a versatile language for
various mathematical computations and scientific applications.

2.7 Numbers - Other Operators

In addition to basic arithmetic operations, Python provides other operators for working with numbers. Here are two
commonly used number operators:

2.7.1 Modulo Operator (%)

The modulo operator, represented by %, calculates the remainder when one number is divided by another. For example:

>>> result = 9 % 2
>>> print(result)
1

The result variable will hold the value 1 because 9 divided by 2 leaves a remainder of 1.

2.7.2 Power Operator (**)

The power operator, represented by **, raises a number to a specified exponent. For instance:

>>> result = 2 ** 3
>>> print(result)
8

The result variable will hold the value 8 because 2 raised to the power of 3 is 8.
These operators expand the range of mathematical operations you can perform in Python, allowing for tasks like finding
remainders and calculating exponents in your numerical computations.

2.7.3 Incrementing Counters

When working with counters in Python, you can increment or decrement their values in various ways. Here are some
common methods:

Using Assignment Operator

You can initialize a counter and then increment it using the assignment operator:

>>> i = 0 # Initialize i to 0
>>> i = i + 1 # Increment i by 1
>>> print(i)
1

After these operations, i holds the value 1.

2.7. Numbers - Other Operators 29


Python for Network Engineer's, Release 2.0

Using Augmented Assignment

A more concise way to increment a counter is to use the augmented assignment operator (+=):

>>> i = 0 # Initialize i to 0
>>> i += 1 # Increment i by 1
>>> print(i)
1

This achieves the same result, with i holding the value 1.

2.7.4 Decrementing a Counter

Decrementing a counter is similar to incrementing, but you subtract a value instead:

>>> i = 10 # Initialize i to 10
>>> i = i - 1 # Decrement i by 1
>>> print(i)
9

You can also use the augmented assignment operator for decrementing:

>>> i = 10 # Initialize i to 10
>>> i -= 1 # Decrement i by 1
>>> print(i)
9

In both cases, i ends up with the value 9.


These techniques are commonly used for maintaining and updating counters in loops, tracking progress, and controlling
the flow of your code when you need to count or iterate through a series of values.

2.8 Type Casting in Python

Type casting is a method used to change the data type of a variable to match the operation you want to perform. In
Python, you can use built-in functions like int(), str(), float(), etc., to achieve this.

2.8.1 Convert an Integer to a Float

You can convert an integer to a float using the float() function.

Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32

Type "help", "copyright", "credits" or "license" for more information.


>>> num = 100 # integer
>>> num = float(num) # convert to float
>>> print(num)
100.0
>>> print(type(num))
<class 'float'>

Here, the integer 100 is converted to a float 100.0.

30 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

2.8.2 Convert a Float to an Integer

You can convert a float to an integer using the int() function.

>>> num = 99.9 # float


>>> num = int(num) # convert to integer
>>> print(num)
99
>>> print(type(num))
<class 'int'>

In this example, the float 99.9 is converted to the integer 99.

2.8.3 Convert a String Literal to an Integer and a Float

You can convert a string literal to an integer and a float using int() and float().

>>> s = '132' # string


>>> n = int(s) # convert to integer
>>> print(n)
132
>>> print(type(n))
<class 'int'>
>>> f = float(s) # convert to float
>>> print(f)
132.0
>>> print(type(f))
<class 'float'>

Here, the string '132' is converted to the integer 132 and the float 132.0.

2.8.4 Note on Converting Strings with Decimals

If your string contains a decimal point, you can’t directly convert it to an integer. First, convert the string to a float and
then to an integer.

>>> s = '132.564' # string


>>> n = int(float(s)) # convert to integer
>>> print(n)
132
>>> print(type(n))
<class 'int'>

In this case, the string '132.564' is first converted to the float 132.564 and then to the integer 132.

2.8. Type Casting in Python 31


Python for Network Engineer's, Release 2.0

2.9 Useful Built-in Functions

Python comes with several built-in functions that you can use right away. Here are some of the most useful ones:

2.9.1 len() Function

The len() function returns the number of characters in a string.

>>> print(len('hello'))
5

This function counts the number of characters in the string 'hello', which is 5.

2.9.2 bin() Function

The bin() function converts an integer to its binary representation.

>>> num = 100


>>> print(bin(num)) # print binary number
0b1100100

The integer 100 is converted to its binary representation 0b1100100.

2.9.3 oct() Function

The oct() function converts an integer to its octal representation.

>>> num = 100


>>> print(oct(num)) # print octal number
0o144

The integer 100 is converted to its octal representation 0o144.

2.9.4 hex() Function

The hex() function converts an integer to its hexadecimal representation.

>>> num = 100


>>> print(hex(num)) # print hex number
0x64

The integer 100 is converted to its hexadecimal representation 0x64.

32 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

2.9.5 round() Function

The round() function returns a floating-point number rounded to a specified number of decimal places.

>>> num = round(5.12345, 2)


>>> print(num)
5.12

Here, the number 5.12345 is rounded to 5.12.

2.9.6 id() Function

The id() function returns a unique identifier for an object, which is its memory address.

>>> num = 5.12


>>> print(id(num))
1562211743472

The id() function returns the memory address of the object num.
These examples demonstrate how you can use type casting and built-in functions to manipulate and format data in
Python.

2.10 Python Operators

Operators in Python are used to perform operations on variables and values. They are divided into the following groups:
• Arithmetic operators
• Assignment operators
• Comparison operators
• Logical operators
• Identity operators
• Membership operators

2.10.1 Arithmetic Operators

Arithmetic operators are used with numeric values to perform common mathematical operations.

Syntax Description Example


+ Addition 3+4
- Subtraction 1-5
* Multiplication 5*6
/ Division 7/2
% Modulus 5%1
** Exponentiation 8 ** 3
// Floor division 3 // 8

2.10. Python Operators 33


Python for Network Engineer's, Release 2.0

2.10.2 Assignment Operators

Assignment operators are used to assign values to variables.

Syntax Example Same as


= x=5 x=5
+= x += 3 x=x+3
-= x -= 3 x=x-3
*= x *= 3 x=x*3
/= x /= 3 x=x/3
%= x %= 3 x=x%3
//= x //= 3 x = x // 3
**= x **= 3 x = x ** 3

2.10.3 Comparison Operators

Comparison operators are used to compare two or more variables.

Syntax Description Example


== Equal x == y
!= Not equal x != y
> Greater than x>y
< Less than x<y
>= Greater than or equal to x >= y
<= Less than or equal to x <= y

2.10.4 Logical Operators

Logical operators are used to combine conditional statements.

Syntax Description Example


and Returns True if both statements are true x < 5 and x < 10
or Returns True if one of the statements is true x < 5 or x < 4
not Reverses the result not(x < 5 and x < 10)

2.10.5 Identity Operators

Identity operators compare the memory locations of two objects.

Syntax Description Example


is Returns True if both variables are the same object x is y
is not Returns True if both variables are not the same object x is not y

34 Chapter 2. Basic Data Types


Python for Network Engineer's, Release 2.0

2.10.6 Membership Operators

Membership operators test if a sequence is present in an object.

Syntax Description Example


in Returns True if a sequence with the specified value is present in the object x in y
not in Returns True if a sequence with the specified value is not present in the object x not in y

2.10. Python Operators 35


Python for Network Engineer's, Release 2.0

36 Chapter 2. Basic Data Types


CHAPTER

THREE

COLLECTIONS AND SEQUENCES IN PYTHON

In Python, collections and sequences are essential tools for grouping multiple values together. They help you manage
and organize data efficiently, whether it’s a list of numbers or a dictionary of key-value pairs.
Collections are like containers that hold multiple items. They come in different types, allowing you to store objects with
or without a specific order. You can add, remove, and iterate over items in a collection. Common types of collections
include:
• Dictionaries: Store key-value pairs and maintain the order of insertion.
• Sets: Store unique elements without any specific order.
Sequences are a type of collection that maintains a specific order of items. The order in which you add items is the
order in which you retrieve them. Common sequence types include:
• Strings: Immutable sequences of characters.
• Lists: Mutable sequences that can store a collection of items.
• Tuples: Immutable sequences that can store a collection of items.
Key Differences
• Order: Sequences maintain a specific order, while collections like sets do not.
• Indexing: You can access items in sequences by their position using an index. Collections like sets and dictio-
naries do not support indexing.
• Mutability: Lists are mutable (you can change them), while tuples and strings are immutable (you cannot change
them). Sets and dictionaries are mutable.
Understanding these concepts will help you effectively manage and manipulate data in your Python programs. Whether
you need to store items in a specific order or just group unique elements together, Python’s collections and sequences
provide the tools you need.

3.1 Understanding Lists in Python

A list in Python is a basic and flexible way to store a collection of items. Lists can hold different types of data, like
strings, numbers, booleans, and even other lists. Let’s explore what makes Python lists so useful.

37
Python for Network Engineer's, Release 2.0

3.1.1 List Basics

A list is a collection of items that:


• Maintains Order: The order in which you add items is the order in which you can access them.
• Holds Mixed Data Types: You can mix different types of data in a single list, like strings, numbers, and more.
• Is Mutable: You can change the items, size, and structure of a list after creating it.
These features make lists a powerful tool for many tasks. In other programming languages, lists are often called arrays,
but Python lists are more flexible.

Creating a List

To create a list, you use square brackets [] and separate items with commas. For example:

my_list = ["foo", 1, "hello", [], None, 2.3]

This list, my_list, contains a mix of strings, an integer, an empty list, None, and a floating-point number.
To check the type of a variable, use the type function:

print(type(my_list))
# Output: <class 'list'>

List Indices

Lists use zero-based indexing, meaning the first item is at index 0, the second at 1, and so on. You can access items
using their index.

Accessing List Elements

To get the first item in my_list:

first_element = my_list[0]
print(first_element)
# Output: foo

To get the second item:

second_element = my_list[1]
print(second_element)
# Output: 1

38 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

Updating a List

Since lists are mutable, you can change their contents. To change the first item to 88:

my_list[0] = 88
print(my_list)
# Output: [88, 1, "hello", [], None, 2.3]

Accessing the Last Element

You can use negative indices to access items from the end of the list. -1 is the last item, -2 is the second-to-last, and
so on:

last_element = my_list[-1]
print(last_element)
# Output: 2.3

Negative indices make it easy to access elements from the end without knowing the list’s length.
Python lists are versatile and powerful, allowing you to store and manipulate collections of data easily.

3.1.2 Length of a List and the Range Function

Understanding how to find the length of a list, use the range function, and check if an element is in a list are important
skills in Python.

Finding the Length of a List

To find out how many items are in a list, you use the len function. This function gives you the number of elements in
the list. For example:

my_list = [10, 20, 30, 40, 50]


list_length = len(my_list)
print("The length of my_list is:", list_length)
# Output: 5

Here, len(my_list) returns 5, meaning there are five items in my_list.

Using the Range Function

The range function is a handy tool for creating sequences of numbers. By default, range starts at 0 and goes up to
(but does not include) the specified stop value. For example, to create a list of numbers from 0 to 4:

numbers = list(range(5))
print(numbers)
# Output: [0, 1, 2, 3, 4]

The range(5) generates numbers from 0 to 4, and list() converts them into a list.

3.1. Understanding Lists in Python 39


Python for Network Engineer's, Release 2.0

Modifying the Range Start Value

You can also specify a starting point for the range function by providing both start and stop values. For example, to
create a list of numbers from 2 to 6:

numbers = list(range(2, 7))


print(numbers)
# Output: [2, 3, 4, 5, 6]

This code generates numbers from 2 to 6 and prints them as a list.

3.1.3 List Membership

You can check if a specific element is in a list using the in operator. This operator returns True if the element is found
in the list and False if it is not. Here’s an example:

fruits = ["apple", "banana", "cherry", "date"]


check_fruit = "banana"

if check_fruit in fruits:
print(check_fruit, "is in the list.")
else:
print(check_fruit, "is not in the list.")

In this case, the output will be:

banana is in the list.

The code checks if banana is in the fruits list and correctly identifies it as a member.

3.1.4 Exploring List Methods

Python lists come with many built-in methods that let you manipulate and work with list elements easily.

The append() Method

The append() method adds an element to the end of a list. For example:

my_list = [1, 2, 3]
my_list.append(4)
print(my_list)
# Output: [1, 2, 3, 4]

This code adds 4 to the end of my_list.

40 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

The clear() Method

The clear() method removes all elements from a list, making it empty:

my_list = [1, 2, 3]
my_list.clear()
print(my_list)
# Output: []

After running this code, my_list will be empty.

The count() Method

The count() method counts how many times a specific element appears in a list:

my_list = [1, 2, 2, 3, 2, 4]
count_of_twos = my_list.count(2)
print("Number of 2s in the list:", count_of_twos)
# Output: Number of 2s in the list: 3

This code shows that 2 appears three times in my_list.

The copy() Method

The copy() method creates a shallow copy of a list:

original_list = [1, 2, 3]
new_list = original_list.copy()

Now, new_list is a copy of original_list, and changes to one won’t affect the other.

The extend() Method

The extend() method adds all elements from another iterable (like another list) to the end of the current list:

list1 = [1, 2, 3]
list2 = [4, 5, 6]
list1.extend(list2)
print(list1)
# Output: [1, 2, 3, 4, 5, 6]

This code combines list1 and list2 into one list.


You can also use the + operator to concatenate lists:

list1 = [1, 2, 3]
list2 = [4, 5, 6]
result = list1 + list2
print(result)
# Output: [1, 2, 3, 4, 5, 6]

3.1. Understanding Lists in Python 41


Python for Network Engineer's, Release 2.0

The pop() Method

The pop() method removes and returns an element from a list at a specified index:

my_list = [1, 2, 3, 4]
popped_element = my_list.pop(2)
print(popped_element)
# Output: 3

This code removes and returns the element at index 2, which is 3.

The remove() Method

The remove() method removes the first occurrence of a specific element from a list:

my_list = [1, 2, 3, 2, 4]
my_list.remove(2)
print(my_list)
# Output: [1, 3, 2, 4]

This code removes the first 2 from my_list.

The sort() Method

The sort() method sorts the elements of a list in ascending order:

my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
my_list.sort()
print(my_list)
# Output: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

This code sorts my_list in ascending order.

The reverse() Method

The reverse() method reverses the elements of a list:

my_list = [1, 2, 3, 4, 5]
my_list.reverse()
print(my_list)
# Output: [5, 4, 3, 2, 1]

This code reverses the order of elements in my_list.


Python lists offer many methods to help you manage and manipulate data effectively. These methods make lists a
versatile and powerful tool for a wide range of programming tasks.

42 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

3.1.5 List Slicing in Python

List slicing is a powerful feature in Python that lets you create new lists from parts of an existing list. It allows you to
extract, manipulate, and work with specific sections of a list without changing the original list.

Basic Slicing

To slice a list, you specify a start and end index within square brackets. For example:

my_list = [1, 'hello', 22, 2.7, 'python']


sliced_list = my_list[1:3]
print(sliced_list)
# Output: ['hello', 22]

This code extracts elements at indices 1 and 2, but not 3.

Omitting the Start or End Index

You can omit the start index to slice from the beginning of the list:

start_from_beginning = my_list[:3]
print(start_from_beginning)
# Output: [1, 'hello', 22]

You can also omit the end index to slice until the end of the list:

end_at_end = my_list[3:]
print(end_at_end)
# Output: [2.7, 'python']

Creating a Copy of a List

To create a copy of the entire list, use an empty slice:

list_copy = my_list[:]

This creates a new list, list_copy, which is a separate copy of my_list.

Negative Index for Slicing

Negative indices allow you to count elements from the end of the list. For example:

negative_index_slice = my_list[3:-1]
print(negative_index_slice)
# Output: [2.7]

This code slices from index 3 to the element just before the last one.
Remember, list slicing does not modify the original list. To use the new slice, assign it to a variable as shown in the
examples.

3.1. Understanding Lists in Python 43


Python for Network Engineer's, Release 2.0

3.1.6 Multidimensional Lists in Python

Multidimensional lists in Python are lists that contain other lists as their elements. These nested lists create a structure
similar to a grid or matrix, allowing you to work with more complex and structured data.

Creating a Multidimensional List

To create a multidimensional list, you include lists as elements within another list. For example:

my_list = [[1, 2, 3], ["hello", "world"]]

Here, my_list is a multidimensional list containing two lists as its elements.

Accessing Lists within a Multidimensional List

To access the lists within a multidimensional list, use indexing. The first index selects a list from the outer list, and the
second index selects an element within the inner list. For example:

first_list = my_list[0] # Select the first list


print(first_list) # Output: [1, 2, 3]

second_list = my_list[1] # Select the second list


print(second_list) # Output: ["hello", "world"]

Chaining Indices for Accessing Elements

To access specific elements within the inner lists, chain the indices. For instance:

element = my_list[0][1] # Access the element at the first index of the first list
print(element) # Output: 2

word = my_list[1][0] # Access the first element in the second list


print(word) # Output: "hello"

In these examples, we first select the list from the outer list and then access elements within that inner list using another
set of indices.
In this overview, we’ve explored the basics of lists in Python. We started by understanding what lists are and their key
characteristics. We learned how to create lists, access their elements using indices, and update their content. We also
covered list slicing, which allows us to extract specific portions of a list without modifying the original. Additionally,
we discovered multidimensional lists, a way to create structured data with nested lists.

3.2 Understanding Tuples in Python

In Python, a tuple is a way to store a collection of items in a specific order. Tuples are similar to lists, but they have
some important differences. Let’s dive into what tuples are, their features, and how you can use them.

44 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

3.3 What is a Tuple?

A tuple is a collection of items that are ordered and can be of different types. You create a tuple using parentheses (),
with items separated by commas. For example:

my_tuple = (1, "hello", 22, None, 2.7)

3.4 Storing Different Data Types in a Tuple

Tuples can hold items of various types. In the example above, my_tuple includes integers, a string, None, and a
floating-point number. This makes tuples very flexible.

3.5 Using Parentheses

To make a tuple, you use parentheses. If you use square brackets [], you’ll create a list instead. So, remember to use
parentheses for tuples:

my_tuple = (1, "hello", 22, None, 2.7)

3.6 Checking the Type of a Tuple

You can check if a variable is a tuple by using the type() function:

type(my_tuple) # Output: <class 'tuple'>

3.7 Immutable Nature of Tuples

Tuples are like lists, but you can’t change them once they’re created. This means you can’t modify their contents. If
you try, you’ll get an error:

my_tuple[0] = 44 # TypeError: 'tuple' object does not support item assignment

3.8 Accessing Elements in a Tuple

You can access items in a tuple using their index, starting from zero. For example, to get the third item in my_tuple:

my_tuple[2] # Output: 22

3.3. What is a Tuple? 45


Python for Network Engineer's, Release 2.0

3.9 Restrictions on Tuple Operations

Because tuples are immutable, you can’t use methods like append(), pop(), or extend() that you can with lists. If
you need to change the items, you should use a list instead.

3.10 Tuple Notation

Tuples are often used for pairs or small sets of related data. For example, you might store IP addresses in a tuple:

ip_addr = ('10.1.1.1', '10.1.1.2')

You can also create a tuple without parentheses, just by separating items with commas:

ip_addr = '10.1.1.1', '10.1.1.2'

Both ways create a tuple, but it’s good to know the difference to avoid confusion.
Tuples in Python are useful for storing ordered collections of different types of items. They are created with parentheses
and can’t be changed after they’re made. Knowing when to use tuples and their limitations will help you write better
Python code.

3.11 Understanding Sets in Python

Sets are a handy and flexible data structure in Python that can handle collections of unique elements. Unlike lists or
tuples, sets are unordered and mutable, meaning you can change their content after creating them. Sets also support
operations like union, difference, and intersection, similar to mathematical sets. In this guide, you’ll learn how to create
and use sets in Python with various examples. You’ll also see how sets can be useful for network engineering tasks,
such as managing unique IP addresses, VLAN IDs, or network devices.

3.12 How to Use Sets in Python

Sets are a type of data structure in Python that store collections of unique items. They’re great for working with distinct
elements, like IP addresses or network devices.

3.13 Creating Sets

To create a set, use curly braces {} and separate the elements with commas. For example, you can create a set of IP
addresses like this:

addresses = {"192.168.100.1", "192.168.100.2", "192.168.100.3"}

This creates a set named addresses with three elements. Remember, sets are unordered, so you can’t access elements
by their position. Also, sets are mutable, so you can change them after creation. One key feature of sets is that they
only allow unique elements. If you try to create a set with duplicate elements, Python will automatically remove the
duplicates. For example:

addresses = {"192.168.100.1", "192.168.100.2", "192.168.100.2"}

46 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

The resulting set will only have two elements because “192.168.100.2” is repeated. This feature is useful for removing
duplicates from a list or other collection.

3.14 Using Sets

Sets in Python are quite versatile and can be used in various scenarios. Here are some common uses:
• Membership Testing: You can use the in operator to check if an element is in a set. This is faster and more
efficient than checking in a list or tuple.
• Mathematical Operations: Sets support operations like union, intersection, difference, and symmetric differ-
ence, which are useful for comparing or combining sets.
• Data Analysis: Sets help find unique items, compare datasets, and more. For example, you can use sets to find
unique words in a text or common elements between two lists.
• Networking: In network engineering, sets can manage unique items like IP addresses, VLAN IDs, or network
devices. For example, you can use sets to check if an IP address is valid or find available IP addresses in a subnet.
These are just a few examples of how sets can be used in Python.

3.15 How to Modify Sets in Python

Sets store collections of unique elements and are mutable, meaning you can change them after creation.

3.15.1 Adding Elements

Use the .add() method to insert a new element into a set. If the element is already in the set, nothing happens. For
example:

addresses = {"192.168.100.1", "192.168.100.2"}


addresses.add("10.1.1.1")
# Output: {'10.1.1.1', '192.168.100.1', '192.168.100.2'}

You can also use the .update() method to merge two sets. This adds all elements from another set to the original set,
removing duplicates. For example:

addresses = {"192.168.100.1", "192.168.100.2"}


addresses.update({"192.168.100.3", "192.168.100.2"})
# Output: {'192.168.100.1', '192.168.100.2', '192.168.100.3'}

3.15.2 Removing Elements

Use the .remove() method to delete a specific element from a set. If the element is not in the set, it raises an error.
For example:

addresses = {"192.168.100.1", "192.168.100.2"}


addresses.remove("192.168.100.1")
# Output: {'192.168.100.2'}

Use the .discard() method to remove an element without causing an error if the element is not in the set. For
example:

3.14. Using Sets 47


Python for Network Engineer's, Release 2.0

addresses = {"192.168.100.1", "192.168.100.2"}


addresses.discard("192.168.100.3")
# Output: {'192.168.100.1', '192.168.100.2'}

Use the .pop() method to remove and return a random element from a set. Since sets are unordered, you can’t predict
which element will be removed. For example:

addresses = {"192.168.100.1", "192.168.100.2"}


addresses.pop()
# Output: '192.168.100.1' (or '192.168.100.2')

These methods make it easy and efficient to modify sets in Python, making them a useful data structure for many
programming tasks.

3.16 How to Perform Set Operations in Python

Sets in Python are not only useful for storing unique elements but also for performing various operations on them. You
can use set operations to combine, compare, and modify sets based on different criteria.

3.16.1 Basic Set Operations: Union, Intersection, and Difference

Sets are a data type in Python that store collections of unique elements. They are useful for performing operations like
union, intersection, and difference.

Union Operation: |

The union operation combines all the elements of two sets and removes any duplicates. To perform a union operation
in Python, you can use the | operator.

sf_addr = {"192.168.100.1", "192.168.100.2", "10.1.1.1"}


la_addr = {"20.1.1.1", "10.1.1.1", "20.1.1.2"}

result = sf_addr | la_addr


# Output: {'10.1.1.1', '20.1.1.1', '20.1.1.2', '192.168.100.2', '192.168.100.1'}

In this example, result will contain all unique elements from both sf_addr and la_addr.

Intersection Operation: &

The intersection operation retrieves the elements that are common to both sets. To perform an intersection operation
in Python, you can use the & operator.

sf_addr = {"192.168.100.1", "192.168.100.2", "10.1.1.1"}


la_addr = {"20.1.1.1", "10.1.1.1", "20.1.1.2"}

result = sf_addr & la_addr


# Output: {'10.1.1.1'}

In this case, result will contain only the element “10.1.1.1” since it’s the common element in both sets.

48 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

Symmetric Difference Operation: ^

The symmetric difference operation retrieves elements that are unique to each set. To perform a symmetric difference
operation in Python, you can use the ^ operator.

sf_addr = {"192.168.100.1", "192.168.100.2", "10.1.1.1"}


la_addr = {"20.1.1.1", "10.1.1.1", "20.1.1.2"}

result = sf_addr ^ la_addr


# Output: {'20.1.1.2', '20.1.1.1', '192.168.100.2', '192.168.100.1'}

Here, result will contain “192.168.100.1,” “192.168.100.2,” “20.1.1.1,” and “20.1.1.2” since these are unique ele-
ments in either sf_addr or la_addr.

Set Subtraction

In Python, you can subtract one set from another to eliminate shared elements. The sequence of subtraction is signifi-
cant, leading to different results. The - operator is used for subtraction.

sf_addr = {"192.168.100.1", "192.168.100.2", "10.1.1.1"}


sf_minus_la = sf_addr - la_addr
# Output: {'192.168.100.2', '192.168.100.1'}

la_addr = {"20.1.1.1", "10.1.1.1", "20.1.1.2"}


la_minus_sf = la_addr - sf_addr
# Output: {'20.1.1.1', '20.1.1.2'}

sf_minus_la will contain elements that are in sf_addr but not in la_addr, while la_minus_sf will contain ele-
ments that are in la_addr but not in sf_addr.
Understanding these fundamental concepts and set operations will empower you to work effectively with sets in Python.
Sets are a valuable data structure for handling collections of unique elements, making them an essential tool for various
programming tasks.

3.17 How Set Operations Can Help Network Engineers

Sets in Python are great for network engineering tasks like managing IP addresses, VLANs, and device inventories.
Let’s see how sets can help with these tasks using some examples.

3.17.1 IP Address Management

Sets can help manage IP address pools by finding available addresses, overlapping addresses, or combining pools. You
can use difference, intersection, or union operations for these tasks. For example:

# Example IP address pools


ip_pool = {'192.168.1.1', '192.168.1.2', '192.168.1.3'}
assigned_ips = {'192.168.1.2', '192.168.1.4'}

# Finding available IP addresses using the difference operation


available_ips = ip_pool - assigned_ips
print("Available IPs:", available_ips)
(continues on next page)

3.17. How Set Operations Can Help Network Engineers 49


Python for Network Engineer's, Release 2.0

(continued from previous page)


# Output: Available IPs: {'192.168.1.1', '192.168.1.3'}

# Detecting overlaps using the intersection operation


overlaps = ip_pool & assigned_ips
print("Overlaps:", overlaps)
# Output: Overlaps: {'192.168.1.2'}

# Combining pools using the union operation


combined_pool = ip_pool | assigned_ips
print("Combined pool:", combined_pool)
# Output: Combined pool: {'192.168.1.1', '192.168.1.2', '192.168.1.3', '192.168.1.4'}

3.17.2 VLAN Management

Sets can help manage VLAN configurations by finding common VLAN IDs or unused IDs. You can use intersection
or difference operations for these tasks. For example:

# Example VLAN configurations


vlan_config1 = {10, 20, 30, 40, 50}
vlan_config2 = {30, 40, 60, 70, 80}

# Finding common VLANs using the intersection operation


common_vlans = vlan_config1 & vlan_config2
print("Common VLANs:", common_vlans)
# Output: Common VLANs: {40, 30}

# Finding unused VLANs using the difference operation


unused_vlans = vlan_config1 - vlan_config2
print("Unused VLANs:", unused_vlans)
# Output: Unused VLANs: {10, 50, 20}

3.17.3 Device Inventory

Sets can help manage device inventories by finding common devices or missing ones. You can use intersection or
difference operations for these tasks. For example:

# Example device inventories


device_inventory1 = {'Router', 'Switch', 'Firewall', 'Load Balancer'}
device_inventory2 = {'Firewall', 'Switch', 'Access Point', 'Router'}

# Finding common devices using the intersection operation


common_devices = device_inventory1 & device_inventory2
print("Common devices:", common_devices)
# Output: Common devices: {'Firewall', 'Switch', 'Router'}

# Finding missing devices using the difference operation


missing_devices = device_inventory1 - device_inventory2
print("Missing devices:", missing_devices)
# Output: Missing devices: {'Load Balancer'}

50 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

These examples show how sets can simplify network engineering tasks, from IP address management to VLAN con-
figurations and device inventory management. Sets ensure data uniqueness, identify common elements, and detect
differences, making Python a powerful tool for network management and automation.

3.18 Python Dictionaries: A Network Engineer’s Guide

Dictionaries in Python are like special containers that store data as key-value pairs. They keep the order of items and
are very useful for many programming tasks. You can create dictionaries using curly braces {} and access values by
their keys. From Python 3.7 onwards, dictionaries are ordered by default, making them even more powerful for writing
organized code.
Think of dictionaries as magical containers that hold key-value pairs. Each key is linked to a specific value, making it
easy to organize and retrieve information. Here’s how you create one:

my_dict = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}


# Output: {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

In this example, 'key1' is linked to 'value1', 'key2' to 'value2', and so on. The curly braces {} enclose the
dictionary, making it a powerful tool for Python programmers.
One of the best things about dictionaries is their flexibility. You can use different data types for both keys and values.
Unlike some other programming languages, Python allows you to use a wide range of data types, including strings,
integers, and even other dictionaries.

mixed_dict = {'name': 'John', 'age': 25, 'grades': {'math': 90, 'science': 85}}
# Output: {'name': 'John', 'age': 25, 'grades': {'math': 90, 'science': 85}}

This flexibility makes dictionaries very versatile, allowing them to hold various types of information within a single
data structure.

3.19 Similarities with Lists: Mutability

Like lists, dictionaries in Python are mutable. This means you can change them after creating them by adding, removing,
or modifying key-value pairs. This flexibility makes dictionaries great for handling changing datasets and adapting to
new requirements.
A Python dictionary is made up of key-value pairs, enclosed in curly braces {}, and can handle various data types.

3.20 Using Curly Braces to Create a Dictionary

The easiest way to create a dictionary is by using curly braces. You define key-value pairs within these braces. Here’s
an example:

my_dict = {
"rtr1": "10.100.1.2",
"rtr2": "10.100.2.1",
"rtr3": "10.100.3.1",
}
# Output: {'rtr1': '10.100.1.2', 'rtr2': '10.100.2.1', 'rtr3': '10.100.3.1'}

In this example, my_dict is a dictionary with router names as keys and their corresponding IP addresses as values.

3.18. Python Dictionaries: A Network Engineer’s Guide 51


Python for Network Engineer's, Release 2.0

3.20.1 Using the dict() Constructor

Python also provides the dict() constructor for creating dictionaries. This method is flexible and can handle various
input formats. Here’s an example:

alt_dict = dict(rtr1="10.100.1.2", rtr2="10.100.2.1", rtr3="10.100.3.1")


# Output: {'rtr1': '10.100.1.2', 'rtr2': '10.100.2.1', 'rtr3': '10.100.3.1'}

In this example, we use keyword arguments within the dict() constructor to create the same dictionary as before. The
keys (rtr1, rtr2, rtr3) and values (10.100.1.2, 10.100.2.1, 10.100.3.1) are passed directly to the constructor.
Choosing between curly braces {} and the dict() constructor depends on your needs. If you have fixed values, curly
braces are quick and easy. If you need more flexibility, like creating dictionaries from variables, use the dict()
constructor. Pick the method that best suits your coding needs.

3.21 Navigating Dictionaries in Python

In Python, dictionaries are powerful tools for storing and managing data. They use keys to uniquely identify values,
making it easy to access and manipulate elements. Here’s a simple example:

# Accessing the value for the key "rtr3"


value = my_dict["rtr3"]

If you try to access a key that doesn’t exist, you’ll get a KeyError. To avoid this, use the get() method, which returns
None if the key is missing:

# Using the get() method to handle missing keys


value = my_dict.get("rtr4")

Dictionaries are not just for retrieving values; you can also update them. To change the value of an existing key, simply
reassign it:

# Assigning a new IP address to the key "rtr3"


my_dict["rtr3"] = "10.100.4.1"

Dictionaries are very efficient for looking up values, even in large datasets, thanks to their underlying hash table imple-
mentation.
You can also add new key-value pairs to a dictionary easily:

# Adding a new key-value pair


my_dict["rtr4"] = "10.100.5.1"

And if you need to update an existing key-value pair, it’s straightforward:

# Updating the IP address for an existing router


my_dict["rtr3"] = "10.100.4.1"

To remove a key-value pair, use the del keyword:

# Deleting a key-value pair


del my_dict["rtr2"]

While dictionaries are mutable, meaning you can change their contents, the keys themselves must be immutable. This
means you can’t use a list as a key, for example, because lists can change.

52 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

3.22 Dictionary Toolbox: Methods

Python dictionaries come with many useful methods for working with their data. These methods help you extract,
manipulate, and manage dictionary contents.

3.23 Exploring Keys, Values, and Items

3.23.1 1. keys(), values(), and items()

These methods let you look inside a dictionary:


• keys(): Gets a list of all keys.
• values(): Gets a list of all values.
• items(): Gets a list of key-value pairs (tuples).

# Example Usage
all_keys = my_dict.keys()
all_values = my_dict.values()
key_value_pairs = my_dict.items()

These methods are useful for iterating over or analyzing the dictionary’s contents.

3.24 Dynamic Modifications with .pop()

3.24.1 The **.pop() Method

The .pop() method retrieves and removes an item from the dictionary:

# Example Usage
value = my_dict.pop("rtr1")

This method gets the value for the specified key and removes the key-value pair from the dictionary.

3.25 Deletion Strategies: del and update

3.25.1 The **del Keyword

The del keyword deletes a key-value pair from the dictionary:

# Example Usage
del my_dict["rtr2"]

3.22. Dictionary Toolbox: Methods 53


Python for Network Engineer's, Release 2.0

3.25.2 The **update() Method

The update() method merges dictionaries and updates existing keys with new values:

# Example Usage
new_data = {"rtr3": "10.100.4.1", "rtr4": "10.100.5.1"}
my_dict.update(new_data)

If there are overlapping keys, the values in my_dict will be updated.

3.26 Dictionary Iteration Techniques

When you loop through a dictionary, you iterate over its keys by default:

# Example Usage
for k in my_dict:
print(k)

This loop prints each key in the dictionary. To iterate over values, use the .values() method:

# Example Usage
for v in my_dict.values():
print(v)

This loop prints each value in the dictionary. To iterate over both keys and values, use the .items() method:

# Example Usage
for k, v in my_dict.items():
print(k, v)

This loop prints each key-value pair in the dictionary.

3.27 Nested Dictionaries in Python

Nested dictionaries in Python are great for representing complex relationships and hierarchies. Let’s explore how to
use them.

3.27.1 Dictionary of Dictionaries

You can have a dictionary where each key’s value is another dictionary. This helps you organize information neatly:

my_devices = {
'rtr1': {
'host': 'device1',
'device_type': 'cisco',
},
'rtr2': {
'host': 'device2',
'device_type': 'junos',
}
}

54 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

Here, each router (‘rtr1’ and ‘rtr2’) has its own dictionary with details like ‘host’ and ‘device_type’.

3.27.2 Chaining Keys for Access

To access information in nested dictionaries, chain the keys together:

# Accessing the device type of 'rtr1'


device_type_rtr1 = my_devices['rtr1']['device_type']

This way, you can navigate through the nested structure.

3.27.3 Dictionary Containing Lists

Dictionaries can also have lists as values:

sf = {
'routers': ['192.168.1.1', '192.168.1.2'],
'switches': ['192.168.1.20', '192.168.1.21']
}

Here, ‘routers’ and ‘switches’ have lists of IP addresses.

3.27.4 Nesting Dictionary Inside a List

You can also nest dictionaries inside a list:

network_devices = [
{'device_type': 'router', 'ip': '192.168.1.1'},
{'device_type': 'switch', 'ip': '192.168.1.20'}
]

This list contains dictionaries, each representing a network device with ‘device_type’ and ‘ip’ attributes.
Nested dictionaries in Python help you structure and organize data efficiently, making it easier to model complex
relationships. Python dictionaries are versatile and essential for organizing data. Their dynamic features and support
for nested structures make them powerful tools in Python programming and network engineering.
Sure! Here’s a simplified explanation of mutable and immutable objects in Python:

3.28 Understanding Mutable and Immutable Objects in Python

In Python, objects can be either mutable or immutable. This means some objects can change their values after creation,
while others cannot.

3.28. Understanding Mutable and Immutable Objects in Python 55


Python for Network Engineer's, Release 2.0

3.28.1 Mutable Objects

Mutable objects can be changed after they are created. Examples include lists, dictionaries, and sets.

3.28.2 Immutable Objects

Immutable objects cannot be changed once they are created. Examples include strings, integers, and tuples.

3.28.3 How Assignments Work

When you assign a value to a variable, Python stores that value in memory, and the variable points to that memory
location.

rtr_addr = "10.1.1.1"

If you assign another variable to the same value, both variables point to the same memory location.

gate_way = rtr_addr

3.28.4 Identifying Objects with id()

You can use the id() function to get the unique identifier of an object in memory.

id(rtr_addr) # Example output: 1513552872240


id(gate_way) # Example output: 1513552872240

3.28.5 Immutable Objects in Action

When you change the value of an immutable object, Python creates a new object in memory.

ssh_timeout = 20
id(ssh_timeout) # Example output: 1513546842960

ssh_timeout = 10
id(ssh_timeout) # Example output: 1513546842640

Even when you increment an immutable object, a new object is created.

ssh_timeout += 1
id(ssh_timeout) # Example output: 1513546842672

56 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

3.28.6 Types of Immutable Objects

• None
• Booleans (True and False)
• Strings
• Integers
• Floats
Understanding these concepts helps you write better Python code and avoid unexpected behavior.
Sure! Here’s a simplified explanation of mutable objects in Python, focusing on lists:

3.29 Mutable Objects in Python

In Python, mutable objects can change their values after they are created. Let’s look at lists as an example.

3.29.1 What Are Mutable Objects?

Mutable objects allow their values to be modified after creation. Examples include lists, dictionaries, and sets. We’ll
focus on lists here.

3.29.2 Working with Lists

Let’s create a list called data_center with some elements.

data_center = ["sf", "la", "den", "dal"]


id(data_center)

The id() function gives the unique identifier for the data_center list, showing its memory location.

3.29.3 Modifying Lists

Now, let’s add a new element, “ny,” to the data_center list.

data_center.append("ny")
id(data_center)

Even after adding an element, the list’s identifier remains the same because lists are mutable. Python doesn’t create a
new list; it modifies the existing one.

3.29. Mutable Objects in Python 57


Python for Network Engineer's, Release 2.0

3.30 How Lists Work Internally

Python allocates a continuous block of memory for a list, but it stores pointers to the elements, not the elements
themselves.

3.30.1 Memory Addresses

A list contains references (pointers) to the actual data objects. Here’s a simplified view:

P1 P2 P3 P4 P5
sf la den dal ny

P1, P2, P3, P4, and P5 are pointers to the elements. The list holds references to where the elements are stored in
memory.
You can check the id() of individual list elements:

id(data_center[0]) # Example output: 1668505141296


id(data_center) # Example output: 1668504824832

3.30.2 Variable Assignment and List Mutability

If you create a new variable, my_dc, and assign it the value of data_center, both variables point to the same list in
memory:

my_dc = data_center
id(my_dc) # Example output: 1668504824832

Modifying my_dc also affects data_center because they refer to the same object. To avoid this, use the copy()
method to create a shallow copy:

my_dc = data_center.copy()
id(my_dc) # Example output: 1668505142144

With a shallow copy, modifying one list doesn’t affect the other.
Understanding mutable objects like lists helps you work effectively with data structures and ensures changes to variables
behave as expected.

3.31 List Comprehensions: Simplifying Data Manipulation

List comprehensions make working with lists in Python easier and more efficient. They are especially useful for network
engineers and Python enthusiasts. Let’s explore the basics and some tips to help you use them effectively.

58 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

3.31.1 Understanding List Comprehensions

List comprehensions provide a simple way to create lists in Python. They follow this structure:
• Syntax: [expression for item in iterable]
• The square brackets indicate that we’re creating a list.
• expression is the value to include in the list for each item in the iterable.
• item is the current element in the iterable.
Here are some basic examples:

Example: Creating a List of Squares

squares = [x**2 for x in range(1, 6)]


# Output: [1, 4, 9, 16, 25]

Example: Filtering Even Numbers

even_numbers = [x for x in range(1, 11) if x % 2 == 0]


# Output: [2, 4, 6, 8, 10]

3.31.2 Simplifying Data Tasks

List comprehensions help generate lists and streamline tasks, making your code clearer and more concise. Here are
some practical examples:

Example: Creating a List of MAC Addresses

network_devices = [
"Router1: AA:BB:CC:DD:EE:FF",
"Switch1: 11:22:33:44:55:66",
"Firewall1: 99:88:77:66:55:44",
]
mac_addresses = [device.split(": ")[1] for device in network_devices]
# Output: ['AA:BB:CC:DD:EE:FF', '11:22:33:44:55:66', '99:88:77:66:55:44']

Example: Generating VLAN IDs

vlan_ids = [str(x) for x in range(1, 11)]


# Output: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']

List comprehensions are great for optimizing code and enhancing network automation. They create new lists based on
existing data without modifying the original list.

3.31. List Comprehensions: Simplifying Data Manipulation 59


Python for Network Engineer's, Release 2.0

3.31.3 Efficient Data Filtering

List comprehensions are excellent for filtering data efficiently. By adding conditions, you can create a new list with
only the elements that meet specific criteria. This is useful for tasks like selecting network devices and extracting data.

Example: Selecting Active Network Devices

network_devices = [
{"name": "Router1", "status": "active"},
{"name": "Switch1", "status": "inactive"},
{"name": "Firewall1", "status": "active"},
]

active_devices = [device for device in network_devices if device["status"] == "active"]


# Output: [{'name': 'Router1', 'status': 'active'}, {'name': 'Firewall1', 'status': 'active'}]

Example: Extracting IP Addresses

configurations = [
"Router1: 192.168.1.1",
"Switch1: 10.0.0.1",
"Firewall1: 172.16.0.1",
]

ip_addresses = [config.split(": ")[1] for config in configurations]


# Output: ['192.168.1.1', '10.0.0.1', '172.16.0.1']

Efficiency is crucial in network engineering, and list comprehensions significantly enhance code efficiency.

3.32 Advanced Techniques with Nested List Comprehensions

Nested list comprehensions are great for handling complex data in network engineering. They help you work with
multi-dimensional data, automate configurations, and visualize network topologies efficiently.

3.32.1 Example: Configuring Access Control Lists (ACLs)

acl_rules = [
{"source": "192.168.1.0/24", "destination": "10.0.0.0/24", "action": "permit"},
{"source": "10.1.1.0/24", "destination": "192.168.2.0/24", "action": "deny"},
# More ACL rules...
]

acl_configurations = [
f"access-list {index} {rule['action']} {rule['source']} {rule['destination']}"
for index, rule in enumerate(acl_rules, start=100)
]

60 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

['access-list 100 permit 192.168.1.0/24 10.0.0.0/24', 'access-list 101 deny 10.1.1.0/24␣


˓→192.168.2.0/24']

3.32.2 Example: Generate a List of IP Addresses

ip_addresses = [f'192.168.{x}.{y}' for x in range(3) for y in range(2)]

['192.168.0.0', '192.168.0.1', '192.168.1.0', '192.168.1.1', '192.168.2.0', '192.168.2.1


˓→']

Mastering nested list comprehensions helps network engineers handle complex tasks and data structures efficiently.

3.33 Using List Comprehensions for Data Transformation

List comprehensions are efficient and concise for data transformation tasks in network engineering. They help convert
data formats, scale values, and clean data.

3.33.1 Example: Converting MAC Addresses to Uppercase

mac_addresses = ["aa:bb:cc:dd:ee:ff", "11:22:33:44:55:66", "99:88:77:66:55:44"]


uppercase_macs = [mac.upper() for mac in mac_addresses]
# Output: ['AA:BB:CC:DD:EE:FF', '11:22:33:44:55:66', '99:88:77:66:55:44']

3.33.2 Example: Data Cleansing – Removing White Spaces

device_names = ["Router 1", "Switch 1", "Firewall 1"]


cleaned_names = [name.replace(" ", "") for name in device_names]
# Output: ['Router1', 'Switch1', 'Firewall1']

List comprehensions make data transformation tasks more readable and streamlined.

3.34 Pros and Cons of List Comprehensions

Pros:
• Readability
• Efficiency
• Simplicity
Cons:
• Can be complex
• Harder to debug

3.33. Using List Comprehensions for Data Transformation 61


Python for Network Engineer's, Release 2.0

Network engineers should use list comprehensions wisely to enhance their tasks.
We’ve explored list comprehensions in Python for network engineers, from basics to advanced techniques. They sim-
plify tasks, optimize code, and enhance network automation. Apply this knowledge to your daily tasks and explore
more resources to master list comprehensions in Python for network engineering projects.

3.35 Set Comprehensions in Python

Set comprehensions in Python allow you to create sets dynamically from iterable objects, which is especially useful in
network automation.

3.35.1 What Are Set Comprehensions?

Set comprehensions are similar to list comprehensions but use curly braces {}. They help create sets from iterables,
which is useful for managing network data.
Example:

network_devices = {"router1", "switch1", "router2", "firewall1", "switch2", "router1"}


unique_devices = {device for device in network_devices}
print(unique_devices)
# Output: {"router1", "switch1", "router2", "firewall1", "switch2"}

This example shows how to create a set of unique network devices.

3.35.2 Advantages and Usage

Set comprehensions automatically remove duplicates. For example, if you have repeated network configurations, a set
comprehension will give you only unique configurations.
Example:

configurations = ["config1", "config2", "config3", "config1", "config4", "config2"]


unique_configs = {config for config in configurations}
print(unique_configs)
# Output: {"config1", "config2", "config3", "config4"}

3.35.3 Using Conditionals

You can filter items in a set comprehension using conditionals. For example, to get only routers from a set of network
devices:
Example:

network_devices = {"router1", "switch1", "router2", "firewall1", "switch2"}


routers_only = {device for device in network_devices if "router" in device}
print(routers_only)
# Output: {"router1", "router2"}

62 Chapter 3. Collections and Sequences in Python


Python for Network Engineer's, Release 2.0

3.35.4 Nested Loops

Nested loops in set comprehensions allow you to combine elements from multiple iterables. This is useful for creating
sets of VLAN configurations.
Example:

vlans = ["vlan10", "vlan20", "vlan30"]


subnets = ["192.168.10.0", "192.168.20.0", "192.168.30.0"]
vlan_subnets = {vlan + " " + subnet for vlan in vlans for subnet in subnets}
print(vlan_subnets)

3.35.5 Complex Sets with Nested Loops and Conditionals

You can use both nested loops and conditionals to create complex sets, such as ACL rules for network security.
Example:

source_addresses = ["192.168.1.0", "192.168.2.0"]


destination_addresses = ["10.0.0.1", "10.0.0.2"]
acl_rules = {source + " to " + destination for source in source_addresses for␣
˓→destination in destination_addresses if "192.168" in source}

print(acl_rules)

3.35.6 Conclusion

Set comprehensions are a powerful feature in Python that can simplify code, manage unique elements, and perform
operations efficiently, making them valuable in network automation.

3.35. Set Comprehensions in Python 63


Python for Network Engineer's, Release 2.0

64 Chapter 3. Collections and Sequences in Python


CHAPTER

FOUR

CONDITIONAL STATEMENTS**

This chapter covers conditional statements, which are widely used in programming. In Python, conditional statements
execute different actions based on whether a condition is True or False. We’ll also look at Boolean expressions in
this chapter.
You can use these conditions in various ways, especially in if statements and loops. Conditional statements in pro-
gramming decide what actions to perform based on the result of a Boolean condition set by the programmer.
If the condition is True, the specified actions are performed. If the condition is False, the actions are not performed.

4.1 Understanding Booleans

Booleans in Python are a basic data type that can be either True or False. They are case-sensitive, so you must write
True and False with an uppercase first letter. Writing them as true or false will cause an error.
To check if a variable is a Boolean, you can use the type() function. For example:

>>> is_true = True


>>> type(is_true)
<class 'bool'>

This code will show that is_true is a Boolean.

4.2 Boolean Logic in Python

Boolean logic is essential in programming because it helps us make decisions and control the flow of our code. Python
has three main Boolean operators: and, or, and not. Let’s see how they work.
Boolean logic is about making decisions based on expressions that are either True or False. These expressions can
be combined using Boolean operators to determine the overall truth of a statement.

65
Python for Network Engineer's, Release 2.0

4.2.1 and Operator

The and operator returns True only if both conditions are True. Otherwise, it returns False.

>>> x = True
>>> y = False
>>> result = x and y
>>> print(result)
False

Here, result is False because both x and y need to be True for the and condition to be True.

4.2.2 or Operator

The or operator returns True if at least one of the conditions is True. It returns False only if both conditions are
False.

>>> a = True
>>> b = False
>>> result = a or b
>>> print(result)
True

In this example, result is True because at least one condition (a) is True.

4.2.3 not Operator

The not operator returns the opposite of the given condition.

>>> z = False
>>> result = not z
>>> print(result)
True

Here, result is True because not inverts the value of z.

4.2.4 Booleans in Conditional Statements

Booleans are often used in conditional statements like if, elif, and else. These statements let your code run different
blocks based on whether conditions are True or False.

>>> value = 42
>>> if value > 50:
... print("Value is greater than 50")
... elif value == 50:
... print("Value is exactly 50")
... else:
... print("Value is less than 50")
...
Value is less than 50

66 Chapter 4. Conditional Statements**


Python for Network Engineer's, Release 2.0

In this example, the code checks the value of value and prints different messages based on the result.
Mastering Boolean logic and conditional statements helps you create dynamic and responsive code, making your ap-
plications more intelligent.

4.3 Truthy and Falsy Values in Python

In Python, values can be “truthy” or “falsy.” Understanding these helps you determine if conditions are met in your
code.

4.3.1 Truthy Values in Python

Truthy values are treated as True in a Boolean context. Examples include:


Non-zero Numbers: Any non-zero number is truthy.

>>> x = 42
>>> if x:
... print("x is truthy")
...
x is truthy

Non-empty Sequences: Lists, tuples, and strings with elements are truthy. Empty sequences are falsy.

>>> my_list = [1, 2, 3]


>>> if my_list:
... print("my_list is truthy")
...
my_list is truthy

Non-empty Containers: Dictionaries, sets, and other containers with elements are truthy.

>>> my_dict = {'key': 'value'}


>>> if my_dict:
... print("my_dict is truthy")
...
my_dict is truthy

4.3.2 Falseness of Values in Python

Falsy values are those that are treated as False in a Boolean context. Examples of falsy values in Python include:
Zero: Both the integer 0 and the floating-point number 0.0 are falsy.

>>> y = 0
>>> if not y:
... print("y is falsy")
...
y is falsy

Empty Sequences: Empty lists, tuples, and strings are considered falsy.

4.3. Truthy and Falsy Values in Python 67


Python for Network Engineer's, Release 2.0

>>> empty_string = ""


>>> if not empty_string:
... print("empty_string is falsy")
...
empty_string is falsy

Understanding truthy and falsy values helps you write more concise and expressive code by simplifying conditional
statements. This makes your code more robust and adaptable to different data scenarios.

4.4 None in Python

In Python, None is a special value that represents the absence of a value. It is often used to indicate that a variable or
function has no meaningful data to return.

4.4.1 No Value in Python

None is used to show that a variable has no value. It’s useful when you want to initialize a variable without giving it
an initial value.

>>> my_variable = None


>>> print(my_variable)
None

Here, my_variable exists but doesn’t have any specific data. It’s like an empty container waiting to be filled.

4.4.2 None Value is False

In a Boolean context, None is considered falsy. This means that None evaluates to False in conditional statements.

>>> value = None


>>> if value:
... print("This will not be printed")
... else:
... print("The condition is not met because value is None")
...
The condition is not met because value is None

In this example, the second print statement runs because value is None, which is falsy. This is useful for checking if
a variable has been assigned a meaningful value.
None is often used as a sentinel value to represent missing or undefined data. Functions that don’t explicitly return a
value will return None by default. Understanding None helps you handle missing data and write more expressive code.

68 Chapter 4. Conditional Statements**


Python for Network Engineer's, Release 2.0

4.5 Conditional Statements in Python

Conditional statements in Python help control the flow of your program. They let you run specific blocks of code based
on whether certain conditions are true or false.
An expression is a piece of code that can be evaluated to produce a value. Expressions usually involve variables,
constants, and operators. Conditions, which determine which code blocks to execute, are made using these expressions.
Here are the main types of conditional statements in Python:
• if statement: This runs a block of code if a condition is true.
• elif statement: This checks multiple conditions one after another. If one condition is true, it runs the corre-
sponding block of code.
• else statement: This runs a block of code if none of the previous conditions are true.
These statements make your Python programs flexible and able to handle different scenarios based on whether condi-
tions are true or false.

4.6 Importance of Conditions in Programming

Conditions are essential in programming because they make your code dynamic and responsive. They allow you to
make decisions based on various factors and guide your program along different paths.
Here’s an example:

ip_addr = "10.1.1.1."
if "10" in ip_addr:
print("Address Found")

In this example, the variable ip_addr holds an IP address as a string. The if statement checks if the string “10” is in
ip_addr. If it is, the code inside the if block runs, printing “Address Found.” The condition "10" in ip_addr is
true if “10” is found in ip_addr, and false otherwise.

4.7 Conditional Statements - elif and else

The elif statement is used to check another condition if the previous if condition is False. If the if condition is
True, the code inside the if block runs. If it’s False, Python checks the condition in the elif statement.
Here’s an example:

ssh_timeout = 20
if ssh_timeout == 10:
print("SSH TimeOut: 10 sec")
elif ssh_timeout > 30:
print("SSH TimeOut Greater Than: 30 sec")
else:
print("Unexpected SSH TimeOut")

In this example:
• If ssh_timeout is 10, it prints “SSH TimeOut: 10 sec.”
• If ssh_timeout is more than 30, it prints “SSH TimeOut Greater Than: 30 sec.”

4.5. Conditional Statements in Python 69


Python for Network Engineer's, Release 2.0

• If neither condition is true, it prints “Unexpected SSH TimeOut.”


The else statement acts as a fallback, ensuring there’s a default action when none of the previous conditions are met.
This combination of if, elif, and else makes your code more flexible and able to handle different scenarios.

4.8 Comparison Operators and Conditionals

Comparison operators help you compare values and make decisions in your code. They are essential for creating
conditions in your programs.
Here are some common comparison operators:
• == (equal)
• != (not equal)
• < (less than)
• > (greater than)
• <= (less than or equal to)
• >= (greater than or equal to)
Let’s look at the previous example again:

ssh_timeout = 20
if ssh_timeout == 10:
print("SSH TimeOut: 10 sec")
elif ssh_timeout > 30:
print("SSH TimeOut Greater Than: 30 sec")
else:
print("Unexpected SSH TimeOut")

In this example, the == and > operators are used to check if ssh_timeout meets certain criteria.
Comparison operators allow you to compare values and create conditions in your code.

4.9 Logical Operators and Conditional Statements

Logical operators help you combine multiple conditions into one expression, making your code’s decision-making
more complex and powerful.
Here’s an example:

ssh_timeout = 20
ip_addr = "10.1.1.1"
host_reachable = True

if host_reachable and ssh_timeout >= 10:


print("Try to connect")
elif not host_reachable or ip_addr == "10.1.1.1":
print("Invalid host, do not try connection")
else:
print("Unexpected error, do something")

70 Chapter 4. Conditional Statements**


Python for Network Engineer's, Release 2.0

In this code:
• The if statement checks if the host is reachable and the SSH timeout is at least 10 seconds.
• The elif statement checks if the host is not reachable or if the IP address is “10.1.1.1”.
• The else statement handles any other cases.

4.10 Nested Conditional Statements

You can place conditionals inside other conditionals to handle multiple conditions.
Here’s an example:

ssh_timeout = 20
host_reachable = True

if host_reachable:
if ssh_timeout is not None:
print("Try to connect")
else:
print("Unexpected error, do something")

In this example:
• The outer if checks if the host is reachable.
• The inner if checks if ssh_timeout is not None.
Nested conditionals let you handle complex decision-making in your code.

4.11 Truthy and Falsy Values in Python

In Python, every value is either truthy or falsy. Truthy values are considered True in conditions, while falsy values are
considered False.
Common truthy values:
• Non-zero numbers
• Non-empty strings
• Non-empty collections
Common falsy values:
• 0 (zero)
• 0.0 (zero float)
• '' (empty string)
• None (no value)
• Empty collections (lists, dictionaries, sets)
Here’s an example:

4.10. Nested Conditional Statements 71


Python for Network Engineer's, Release 2.0

ssh_timeout = 0

if not ssh_timeout:
print("Error, no SSH timeout")
# Output: Error, no SSH timeout

In this code, ssh_timeout is 0, a falsy value. The condition if not ssh_timeout checks if ssh_timeout is falsy,
and if it is, it prints “Error, no SSH timeout.”
Using truthy and falsy values helps you create flexible and adaptive code.

4.12 Idiomatic Expressions in Python

Idiomatic expressions in Python are about writing code that is clear, efficient, and easy to understand. Using idiomatic
expressions improves code readability.

ssh_timeout = 20
ip_addr = None
host_reachable = False

if ssh_timeout is None: # ssh_timeout == None is not idiomatic


print("Error, no SSH timeout")

• Idiomatic: Use is None to check if a variable is None. This is clearer and more explicit than using
ssh_timeout == None.

if host_reachable is False: # host_reachable == False


print("Error, host is not reachable")

Instead of explicitly checking if a boolean variable is False, you can use the variable itself in a boolean context. For
example, if not host_reachable: is more readable than host_reachable == False.

if ip_addr is not None: # ip_addr != None


print("Error, no SSH timeout")

Use is not None to check if a variable is not None. This is the recommended practice instead of ip_addr != None.
Using idiomatic expressions improves code consistency and maintainability. Python linters, which are tools for check-
ing code quality, can help catch non-idiomatic expressions and ensure your code follows best practices.
Conditionals are essential in programming for making decisions, controlling the flow of your code, and handling differ-
ent scenarios. With comparison and logical operators, you can create dynamic and responsive code that meets various
conditions and requirements. Whether dealing with simple choices or complex decision-making, conditionals are a
crucial tool in your programming toolkit.

72 Chapter 4. Conditional Statements**


CHAPTER

FIVE

LOOPS IN PYTHON

Loops are a basic part of all programming languages. They let you run a set of instructions over and over until a certain
condition is met. In Python, there are two main types of loops:
• For loop
• While loop

5.1 Understanding the For Loop in Python

In Python, you can loop through collections or sequences in different ways, and one of the most common methods is
the for loop. The for loop lets you run a block of code for each item in an iterable, like a list, string, tuple, set, or
dictionary. Let’s break down how a for loop works with an example:

ip_list = ["10.88.17.1", "10.88.17.2", "10.88.17.20", "10.88.17.21"]


for ip in ip_list:
print(ip)

Here’s a breakdown of the key parts and how they work:


1. for keyword: This starts the loop. It tells Python you want to begin a loop.
2. Loop variable (ip): In for ip in ip_list:, ip is the loop variable. It’s a temporary variable that represents
each item in the iterable during each loop cycle.
3. Looping object (ip_list): ip_list is the list you want to loop over. In each loop cycle, the loop variable ip
takes the value of the next item in the list.
4. Indented block: The indented code under the for loop is what runs in each loop cycle. Here, it’s print(ip).
Each time the for loop runs, the loop variable ip gets the value of the next element in ip_list. The loop continues
until all elements in the list are processed.
You can use a for loop to iterate over different types of iterable objects, like lists, strings, tuples, sets, or even dictio-
naries (where you’ll loop over the keys by default).
In this example, the for loop goes through ip_list, and in each loop cycle, it prints the IP address. The loop continues
until all IP addresses in the list are printed.

73
Python for Network Engineer's, Release 2.0

5.1.1 Break - Exiting a Loop in Python

The break statement is used to exit a loop early. It lets you stop the loop when a specific condition is met. Let’s see
how the break statement works with an example:

for i in range(10):
print(i)
if i == 5:
break
print(f"Outside loop --> {i}")

Here’s a step-by-step explanation of the code:


1. for loop: The for loop goes through a range of numbers from 0 to 9, created using range(10). In each loop
cycle, the loop variable i takes the value of the next number in the range.
2. print(i): Inside the loop, the value of i is printed. This line prints the current value of i in each loop cycle.
3. if i == 5:: This line checks if the value of i is 5. When i becomes 5, the condition is met.
4. break statement: When the condition i == 5 is met, the break statement runs. This statement immediately
exits the loop, even if there are more cycles left. In this case, it exits the loop when i is 5.
When you run this code, you will see the following output in the console:

0
1
2
3
4
5
Outside loop --> 5

As you can see, the loop starts from 0 and increments i in each cycle. When i becomes 5, the break statement runs,
causing the loop to exit immediately. The “Outside loop –> 5” message is then printed to the console. The break
statement is useful for controlling the flow of your loops and exiting them when a specific condition is met.

5.1.2 Continue - Skipping an Iteration in a Loop

The continue statement is used to skip the current iteration of a loop and move to the next one. Unlike the break
statement, continue doesn’t exit the loop; it just skips the current iteration and goes to the next. Let’s see how the
continue statement works with an example:

for i in range(10):
print(i)
if i == 5:
continue
print(f"Outside loop --> {i}")

Here’s a step-by-step explanation of the code:


1. for loop: The for loop goes through a range of numbers from 0 to 9, created using range(10). In each loop
cycle, the loop variable i takes the value of the next number in the range.
2. print(i): Inside the loop, the value of i is printed. This line prints the current value of i in each loop cycle.
3. if i == 5:: This line checks if the value of i is 5. When i becomes 5, the condition is met.

74 Chapter 5. Loops in Python


Python for Network Engineer's, Release 2.0

4. continue statement: When the condition i == 5 is met, the continue statement runs. This statement causes
the loop to skip the rest of the current iteration and move to the next one. In this case, it skips printing 5.
When you run this code, you will see the following output in the console:

0
1
2
3
4
6
7
8
9
Outside loop --> 9

As you can see, the loop starts from 0 and increments i in each cycle. When i becomes 5 and the if i == 5:
condition is met, the continue statement runs, causing the loop to skip the print statement for 5. However, the loop
continues, and the “Outside loop –> 9” message is printed at the end, showing the value of i when the loop completes.
The continue statement is useful for controlling the flow of your loops and skipping specific iterations based on certain
conditions, without exiting the loop entirely.

5.2 Nesting Loops in Python

Nesting loops in Python means putting one loop inside another. This lets you create more complex patterns and work
with multiple levels of data. Let’s see how nested loops work with an example:

data_centers = [("sf1", "10.1.1"), ("sf2", "10.2.2")]

for dc, ip in data_centers:


for i in range(1, 3):
print(f"{dc} --> {ip}{i}")

Here’s a step-by-step explanation of the code:


1. data_centers: This is a list of tuples with data center information. Each tuple has two values: the data center
identifier (dc) and an IP address prefix (ip).
2. Outer for loop: The outer for loop goes through the data_centers list. In each loop cycle, dc gets the first
value of the tuple, and ip gets the second value.
3. Inner for loop: Inside the outer loop, there’s another for loop. This inner loop generates numbers from 1 to 2
(using range(1, 3)). In each cycle of the inner loop, i gets the next number in the range.
4. print(f"{dc} --> {ip}{i}"): Inside the inner loop, this line prints a formatted string combining the data
center identifier (dc), the IP address prefix (ip), and the value of i. This line runs for each combination of dc
and i in the current outer loop cycle.
When you run this code, you will see the following output in the console:

sf1 --> 10.1.11


sf1 --> 10.1.12
sf2 --> 10.2.21
sf2 --> 10.2.22

5.2. Nesting Loops in Python 75


Python for Network Engineer's, Release 2.0

The code shows how nested loops work. The outer loop goes through the data center information, and for each data
center, the inner loop generates IP addresses with different numerical suffixes. This gives you a combination of data
center names and IP addresses with varying suffixes in the output.
Nesting loops is a powerful technique that lets you work with multi-dimensional data structures and perform more
complex iterations and computations in your Python programs.

5.2.1 Enumerate in Python: Getting Index and Item

In Python, the enumerate function is a handy tool for looping through a sequence (like a list) while keeping track of
both the index and the item at that index. This is useful when you need to know the position of items in the sequence.
Let’s see how the enumerate function works with an example:
data_centers = ["sf1", "sf2", "la1", "la2"]
for i, dc in enumerate(data_centers):
print(f"{i} --> {dc}")

Here’s a step-by-step explanation of the code:


1. data_centers: This is a list containing the names of data centers.
2. enumerate(data_centers): The enumerate function is called on the data_centers list. It returns an iter-
ator that generates pairs of index-value tuples. For each element in the data_centers list, enumerate returns
a tuple with the index (i) and the item (dc) at that index.
3. for i, dc in enumerate(data_centers):: This line sets up a for loop that goes through the pairs gener-
ated by enumerate. In each loop cycle, i gets the index, and dc gets the item from the current pair.
4. print(f"{i} --> {dc}"): Inside the loop, this line prints a formatted string showing the index i followed by
an arrow (-->) and the data center name dc. This line runs for each element in the list, displaying the index and
the corresponding data center name.
When you run this code, you will see the following output in the console:
0 --> sf1
1 --> sf2
2 --> la1
3 --> la2

The enumerate function makes it easy to access both the index and the item in a sequence, making it a convenient
choice when working with ordered data, like lists. It’s especially useful when you want to process or display items in
a list along with their positions.

5.2.2 Using else with a for Loop**

In Python, you can add an else block to a for loop. The code inside the else block runs when the loop has gone
through all its elements without hitting a break statement. This is useful when you want to do something only if the
loop finishes completely without stopping early.
Here’s an example to show how the else block works in a for loop:
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
if fruit == "orange":
print("I found an orange!")
break
(continues on next page)

76 Chapter 5. Loops in Python


Python for Network Engineer's, Release 2.0

(continued from previous page)


else:
print("No oranges found in the list.")

In this code, we have a list of fruits, and the for loop goes through each fruit. If the loop finds an “orange,” it prints a
message and breaks out of the loop. But if no “orange” is found during the whole loop, the else block runs and prints
“No oranges found in the list.” This shows how the else block can be used to handle the case when the loop finishes
without finding a specific item.

5.3 While Loop in Python

A while loop is a powerful tool in Python that lets you repeatedly run a block of code as long as a certain condition is
true. It’s useful when you want to run code based on a condition that might not be known beforehand.
A while loop has an expression followed by a colon and an indented block of code. The loop keeps running as long as
the expression is True.

while expression:
print("message")

This code will keep printing “message” as long as the expression is true.
A common mistake with while loops is creating an infinite loop, where the loop never stops. To avoid this, make sure
the expression in the while loop will eventually become false, or use the break keyword to exit the loop.

5.3.1 While True - Creating an Infinite Loop

Sometimes, you might want to create an infinite loop that runs until a certain condition is met. You can do this with a
while True loop and a break statement inside it.

while True:
user_input = input("Enter 'exit' to quit: ")
if user_input == 'exit':
break
print("You are still inside the loop!")

In this case, the loop keeps running until the condition is met, and then the break statement exits the loop.

5.3.2 Nesting Loops

You can also nest while loops, meaning you can have a while loop inside another while loop or even a for loop inside
a while loop. This is useful for handling complex control flow and working with multi-dimensional data.

5.3. While Loop in Python 77


Python for Network Engineer's, Release 2.0

5.4 For vs. While Loops

While loops are similar to for loops. They both support the break, continue, and else statements for controlling the
loop’s flow.
• For loops are typically used when you want to iterate over a collection, like a list or range of numbers.
• While loops are more event-based and are used when you have specific conditions to start and stop the loop.
While loops are essential in Python for running code until a certain condition is met. Understanding how to use them
can help you control the flow of your programs. They are a valuable tool in your programming toolkit.

78 Chapter 5. Loops in Python


CHAPTER

SIX

FILE HANDLING IN PYTHON

Python makes it easy to work with files. You can read from and write to files without much hassle. This is espe-
cially useful in network automation, where you might need to read configuration snippets or save backups of your
configurations. Python provides several functions to create, read, update, and delete files.

6.1 Working with Files in Python

Working with files in Python is a common task. It’s important to know how to read and manipulate file contents. This
guide will start with the basic method and then explore other ways to read and write files.

6.1.1 File Reading in Python

Reading a file in Python lets you access its contents. Here’s the basic method:

f = open('show_version.txt')
data = f.read()
f.close()

Let’s break down the code:


1. Opening the File: Use the open() function to open a file. In this example, ‘show_version.txt’ is the file you
want to read. If the file is in the same directory as your Python script, just provide the filename. The open
function returns a file object, which we assign to the variable f.
2. Reading the File: After opening the file, use the read() method to get its contents. Here, we read the entire
file as a string and store it in the data variable.
3. Closing the File: It’s important to close the file after reading it using the close() method. Not closing the file
can cause resource leaks and other issues. Although Python can automatically close the file when the script ends,
it’s best practice to close it explicitly.
By default, the file is opened in 'r' (read) mode, which treats it as a text file. If you need a different mode or want to
open a file in binary mode, you can specify it as the second argument to the open function.

79
Python for Network Engineer's, Release 2.0

6.1.2 Other Methods for Reading a File

There are several ways to read a file in Python, each suited to different needs:

Reading Line by Line (readline)

The readline() method reads one line at a time and moves the file pointer to the next line. You can use a loop to read
each line in sequence. If you need to re-read the file, reset the file pointer to the beginning with f.seek(0).

f = open('show_version.txt')
line = f.readline()
while line:
print(line)
line = f.readline()
f.close()

Reading All Lines into a List (readlines)

The readlines() method reads all the lines of a file into a list, with each line as a separate element.

f = open('show_version.txt')
lines = f.readlines()
f.close()

for line in lines:


print(line)

Looping Over a File

You can also loop directly over the file object. This reads the file line by line without needing readline or readlines.

f = open('show_version.txt')
for line in f:
print(line)
f.close()

These are the basic and common techniques for reading a file in Python. Choose the method that best fits your needs.
Always remember to close the file when you’re done to manage resources properly and avoid issues.

6.1.3 File Writing in Python

Python not only lets you read files but also create and modify them. Let’s look at how to write to a file in Python,
including opening a file for writing, adding content, and what happens when you write to a file.

80 Chapter 6. File Handling in Python


Python for Network Engineer's, Release 2.0

Writing to a File

To write to a file in Python, you need to open it in write mode and use the write() method to add content. Here’s an
example:

f = open('show_version.txt', "w")
f.write("This is the 1st line to write...\n")
f.write("This is the 2nd line to write...\n")
f.close()

Here’s a breakdown of the code:


1. Opening the File for Writing: Use the open() function to open a file in write mode. In this example,
‘show_version.txt’ is the file you want to write to, and “w” specifies write mode. If the file doesn’t exist, it
will be created. If it does exist, its previous contents will be overwritten, so be careful when opening an existing
file in write mode.
2. Writing to the File: Use the write() method to add content to the file. In this case, we add two lines of text,
each followed by a newline character (\n) to separate the lines.
3. Closing the File: It’s important to close the file after writing to it using the close() method. Not closing the
file can result in incomplete or corrupted data.
Writing to a file in Python is a destructive operation when you open a file in write mode. If the file already has content,
that content will be overwritten. Only the new content you write will remain.

6.1.4 Appending to a File

Appending to a file in Python lets you add new content to an existing file without erasing what’s already there. Here’s
how to do it:
To append to a file, open it in append mode (“a”) instead of write mode (“w”). Here’s an example:

f = open("test_file.txt", mode="a")
f.write("Hello again\n")
f.flush()
f.close()

Here’s a step-by-step breakdown:


1. Opening the File in Append Mode: Use the open function to open ‘test_file.txt’ in append mode by specifying
“a”. Unlike write mode, append mode doesn’t erase the file’s current contents. It places the file pointer at the
end, so new content is added after the existing content.
2. Writing to the File: Use the write method to add new content. In this example, “Hello again” followed by a
newline character is written to the file, appending it to the end.
3. Flushing the Buffer (Optional): It’s a good practice to flush the buffer after writing. The flush method ensures
any pending data is immediately written to the file. This isn’t always necessary, but it helps ensure data is written
when expected.
4. Closing the File: Always close the file when you’re done using the close method. This saves the file properly
and releases system resources.
Appending mode is useful for adding new data to files without deleting existing content. It’s great for maintaining a
file’s history or continuously updating its contents.
In summary, understanding different file modes like “a” for appending is essential when working with files in Python.
It lets you add to files while keeping their existing data intact.

6.1. Working with Files in Python 81


Python for Network Engineer's, Release 2.0

6.1.5 Python Context Managers - “with”

Python makes it easy to work with resources like files that need to be opened and closed properly. This is done using
context managers, mainly with the “with” keyword. Let’s see how to use them with files.

The “with” Keyword

The “with” statement in Python helps manage resources like files. It opens a file, lets you work with it, and then
automatically closes it when you’re done, even if there’s an error. Here’s an example of using “with” to read a file:

with open("show_version.txt", mode="r") as f:


data = f.read()

Let’s break down the code:


1. Opening the File: The “with” statement opens ‘show_version.txt’ in read mode (“r”) and assigns the file object
to f.
2. Performing Operations: Inside the “with” block, you can do things with the file. Here, we read the file’s
contents into the data variable.
3. Automatic Closure: The “with” statement ensures the file is automatically closed when you exit the block, even
if there’s an error.

Why Use Context Managers?

Using the “with” statement has several benefits:


• Clean Code: It makes your code cleaner and easier to read by handling resource management for you.
• Resource Management: It ensures the file is always closed properly, even if an error occurs.
• Improved Safety: It reduces the risk of forgetting to close a file, which can cause problems.
• Simplified Error Handling: It makes error handling easier since you don’t have to worry about closing the file
yourself.
In summary, context managers with the “with” statement are very useful in Python for managing resources like files.
They make your code simpler, safer, and more readable by automatically handling resource cleanup.
For more detailed information, you can refer to the official Python documentation:
1. File Modes: Python File Modes
2. Context Managers and with Statement: The with Statement
The official documentation provides in-depth information on Python’s file handling, context managers, and more.

82 Chapter 6. File Handling in Python


CHAPTER

SEVEN

EXCEPTION HANDLING IN PYTHON FOR NETWORK ENGINEERS

Handling exceptions is a fundamental skill for Python programmers and network engineers who navigate complex
systems. This chapter will help you understand how to deal with errors in Python and why it’s so important, especially
for network engineers.

7.1 Understanding Exceptions

Exceptions are events that occur when something goes wrong in the code, such as a syntax error, division by zero, or
a missing file. When an exception occurs, Python stops the normal execution of the code and jumps to a special block
of code called an exception handler.
The exception handler can either fix the problem and resume normal execution or terminate the program gracefully and
report the error to the user. By using exception handling, you can make your code more robust and reliable, avoiding
unexpected crashes or data loss.
Exceptions are objects that represent the occurrence of an abnormal condition that interrupts the normal flow of the
program. When an exception is raised, the program stops executing the current statement and looks for a way to handle
the exception. For example, consider this Python code:

my_list = [] # empty list


print("Trying to access an invalid index")
print(my_list[0]) # This causes an exception

Running this code produces a traceback, which shows the following information:
• The file name and line number where the exception occurred.
• The line of code that caused the exception.
• The type of exception that was raised (IndexError in this case).
• A message that explains what went wrong (list index out of range).
By reading the traceback, you can understand what kind of error happened and where it happened in your code. This
helps you debug and fix the problem quickly.

83
Python for Network Engineer's, Release 2.0

7.2 Handling Exceptions

Errors are unavoidable in programming, but Python provides developers, including network engineers, with a powerful
way to deal with them. The main tools for this are the try and except blocks. Let’s see how these blocks work and
why they are important for ensuring smooth program execution.

7.2.1 The try Block

The try block is where we put the code that might cause exceptions. When Python sees a try block, it tries to run the
code inside it. If an exception happens during this run, control is passed to the corresponding except block.

7.2.2 The except Block

The except block is where we define what to do when exceptions happen. It acts as a backup plan, allowing the
program to handle errors without stopping abruptly. By naming the exception type to catch, we can customize our
response to different situations.
Let’s look at our previous example and add exception handling:

try:
my_list = []
print(my_list[0]) # Exception in code
except IndexError:
print("Index doesn't exist.") # Handling the exception

In this updated code snippet:


• The try block contains the code that might raise an exception.
• The except block specifies what to do when an IndexError occurs, preventing the program from crashing.
The purpose of handling exceptions is:
• Graceful Degradation: Exception handling ensures that the program gracefully degrades when errors occur,
allowing it to recover and keep running.
• Error Isolation: By naming specific exception types, we can isolate and fix different kinds of errors.
• Enhanced Debugging: Well-written exception handling provides useful information in error messages and
tracebacks, making debugging easier.

7.3 Handling Specific Exceptions

Network engineers often encounter situations where certain types of mistakes happen more often or have different
consequences. In such cases, handling exceptions becomes more effective when tailored to the nature of the potential
errors.
Consider a scenario where a Python script involves establishing an SSH connection, a common task for network engi-
neers. The script might encounter an exception if the SSH connection fails. To address this specific error, we can use
the except block with the appropriate exception type, such as SSHException:

84 Chapter 7. Exception Handling in Python for Network Engineers


Python for Network Engineer's, Release 2.0

from paramiko import SSHException

try:
# Code for SSH connection
# Your SSH connection code goes here

except SSHException as e:
print("SSH Connection failed.") # Handling SSH exception

In this example:
• The try block encapsulates the code responsible for establishing the SSH connection.
• The except block specifically targets the SSHException type, allowing for a tailored response in the event of
an SSH connection failure.
In a network engineering context, handling specific exceptions can be applied to scenarios like dealing with connection
timeouts, authentication failures, or protocol-specific issues. This level of detail in handling exceptions helps network
engineers write strong and reliable programs that can handle different error situations well.

7.4 Handling Multiple Exceptions

Sometimes, a piece of code may encounter different types of errors, depending on the input or the environment. In
such cases, handling multiple exceptions within a single except clause can help programmers handle various error
scenarios more efficiently.
The syntax for handling multiple exceptions in Python is to specify a tuple of exception classes within the except
clause. This allows the clause to catch and handle any of the exceptions listed in the tuple. For example:

try:
x = int(input("What's x? "))
result = x / 2

except (TypeError, ValueError):


print("A type or value error occurred.")

In this example:
• The try clause contains code that may raise either a TypeError or a ValueError, depending on the input or
the environment.
• The except clause catches and handles either of these exceptions, printing a generic message.
Additionally, programmers can define different except clauses for different exception classes. This allows for cus-
tomizing the response based on the specific nature of each exception.

7.4. Handling Multiple Exceptions 85


Python for Network Engineer's, Release 2.0

7.5 Exceptions as Variables

Python programming offers many features that enhance the error-handling process, and one of them is the ability to
capture exceptions as variables. This feature is particularly useful for network engineers who want to understand the
root causes of errors and troubleshoot them effectively.
When an exception occurs, Python allows assigning it to a variable that contains the exception instance. This variable
gives access to attributes such as the exception type and error message. For example:

try:
10 / 0
except ZeroDivisionError as e:
print(f"An error occurred. Reason: {e}")

In this example:
• The try block contains the code that may raise an exception.
• The except block catches the exception as the variable e.
• The print statement shows information about the type and message of the caught exception.
Capturing exceptions can provide valuable insights, but it also requires caution, especially when using a generic except
clause like except Exception.
This approach catches all exceptions, including those inherited from the base Exception class. While it can be helpful
for general debugging, it may also hide specific problems or cause unwanted side effects.
In addition to capturing exceptions, Python has a feature called the finally clause, which lets developers write cleanup
code that runs regardless of whether an exception happens or not. This is useful for things like closing network con-
nections or freeing up resources.

try:
# Code that might cause an exception
except Exception as e:
print(f"An exception happened: {str(e)}")
finally:
# Cleanup code
print("Cleanup code run.")

Exception handling in Python is important because it helps fix mistakes and make Python scripts more reliable in
complicated networking situations.
By implementing these techniques and best practices, network engineers can elevate their Python scripts to new levels
of reliability and resilience in the ever-evolving landscape of network engineering.
For a full list of built-in exceptions, check the Python documentation.

86 Chapter 7. Exception Handling in Python for Network Engineers


CHAPTER

EIGHT

FUNCTIONS AND CLASSES

Like any other programming language, some codes are used more than once and need not be rewritten over and over.
These repeatable codes are called Functions.
Python is known to be an object-oriented language. This is because almost everything in Python is objects which are
associated with functions (methods) and properties (attributes) and variables. Once a class is defined, we can create
instances from a class, this class serves as a blueprint for subsequent instances.
• Python Naming Convention
• For function names, object names, and module names use lower case separated by underscore, for example:

def my_router():
pass

• For class names, capitalize the first letter of each word. Do not use any underscores. For example:

class UserProfile:
pass

8.1 Functions and Classes

In Python, the concept of functions plays a crucial role, allowing developers to encapsulate and reuse code efficiently.
Functions provide a way to create modular and maintainable code, as specific tasks can be performed by calling a
function multiple times, eliminating the need for redundant code.
Python’s strength in object-oriented programming (OOP) further enhances its capabilities. In Python, almost everything
is treated as an object, each associated with functions (methods), properties (attributes), and variables. A class serves
as a blueprint in OOP, defining the structure and behavior of objects. Instances of a class can be created, essentially
representing concrete examples based on the class’s specifications.
When naming functions, objects, and modules in Python, adhere to the convention of using lowercase letters separated
by underscores. For instance:

def my_router():
pass

On the other hand, when naming classes, follow the convention of capitalizing the first letter of each word without
using underscores. For example:

class UserProfile:
pass

87
Python for Network Engineer's, Release 2.0

This naming convention enhances code readability and aligns with Python’s style guidelines.
Python’s functions are blocks of organized code designed to be reusable for specific tasks. They allow the efficient
reuse of code segments and the creation of customized functions is straightforward. The syntax for defining a function
in Python involves using the def keyword.

def configure_router(router_name):
print(f"{router_name} Configuration is completed.")

configure_router("R1") # Calling the Function

Functions commence with the def keyword, followed by the function name and parentheses, which may include input
parameters. The code block for each function is indicated by a colon. To invoke a function, simply use its name followed
by parentheses. This structure promotes code clarity and the modular design of programs.
There are two types of functions in Python:
• Built-in Functions
• User-defined Functions

8.2 Built-in Functions

Built-in functions are pre-written functions provided by the Python language and stored in its libraries. These functions
are readily available for use and help streamline common tasks without the need to write code from scratch. Python
boasts a range of built-in functions, including widely-used ones like dir() and sum().

8.3 User-defined Functions

Python allows the creation of user-defined functions, which are functions declared by the programmer within their
code. These functions are tailored to specific needs and tasks, providing a way to organize and reuse code for custom
functionalities.
This distinction between built-in and user-defined functions showcases the versatility of Python, enabling developers
to leverage both pre-existing functionalities and create their own tailored solutions.

8.4 Return Values

In Python, if you do not explicitly specify a return value for a function, it automatically returns None. Let’s explore
this concept by calling a function and assigning its result to a variable.

def configure_device(device_name):
print(f"Configuring {device_name}")
# Note: No explicit return statement, so it defaults to None

result = configure_device("Router1")
print(result) # Output: None

In the example above, the configure_device function configures a network device and does not have a specific return
statement, thus defaulting to None.
To make a function return a value, the return statement is used:

88 Chapter 8. Functions and Classes


Python for Network Engineer's, Release 2.0

def get_device_status(device_name):
# Simulating some network automation logic
status = "Online"
return f"{device_name} is {status}"

device_status = get_device_status("Switch2")
print(device_status) # Output: Switch2 is Online

In this network automation-related example, the get_device_status function simulates network logic and returns
the status of a device. The returned value can be assigned to a variable for further use.
Furthermore, if a function does not encounter a return statement, or if it encounters a return statement without
an expression, it automatically returns None. This behavior is designed to handle cases where a function is primarily
intended for its side effects, such as printing information or modifying external variables, rather than producing a
specific result.
Here’s an example to illustrate this:

def print_message(message):
print(message)

result = print_message("Hello, World!")


print(result) # Output: None

In this example, the print_message function prints a message but does not have a return statement. When the
function is called and assigned to the variable result, result holds the value None because the function does not
explicitly return anything.
It’s worth noting that in Python, a function doesn’t always have to return a value. Some functions are designed for
their side effects, and the absence of a return statement implies a default return of None. If you need a function to
explicitly return a value, you use the return keyword followed by the expression to be returned.

def add_numbers(a, b):


return a + b

result = add_numbers(3, 5)
print(result) # Output: 8

In the add_numbers function, the return a + b statement explicitly returns the sum of a and b. If there were no
return statement in this function, it would also return None by default.

8.5 Function Arguments

In most functions, you can pass arguments to them. This is because you typically want to provide one or more values
to a function, allowing it to perform actions based on these inputs.
In Python, the terms “parameter” and “argument” are often used interchangeably. However, there is a distinction
between these two terms. Parameters are the variables defined when creating a function, while arguments are the
actual values assigned to these parameters when the function is called.

8.5. Function Arguments 89


Python for Network Engineer's, Release 2.0

8.6 Positional Arguments

Positional arguments involve passing values to a function based on the order in which parameters are listed during the
function definition. The order of these values is crucial, as they are assigned to corresponding parameters based on
their position.
Consider the following example, where a function takes a single positional argument, device_name. When the function
is called, a device name is provided, and it is used inside the function to print relevant information.

def configure_device(device_name):
print(f"Configuring {device_name}")

configure_device("Router1")
# Output: Configuring Router1

Here, the function configure_device expects a device name as a positional argument, and when calling the function
with "Router1", the device name is utilized inside the function for configuration.
When working with functions, arguments are specified after the function name, inside the parentheses. You can include
as many positional arguments as needed, separating them with commas for clear organization and functionality.

8.7 Default Arguments

Default arguments provide a way to make your functions callable with fewer arguments, offering flexibility in function
calls. Unlike required arguments, which must be passed for the function to execute a task, default arguments come with
predefined values.
Consider the following example, where a function configure_device has a regular argument, device_name, and a
default argument, configuration_mode, which defaults to “basic.”

def configure_device(device_name, configuration_mode="basic"):


print(f"Configuring {device_name} in {configuration_mode} mode.")

configure_device("Router1")
# Output: Configuring Router1 in basic mode.

In this example, the configure_device function takes a regular argument device_name and a default
argument configuration_mode, which defaults to “basic.” When calling the function without specifying
configuration_mode, it defaults to the predefined value.
You can also explicitly specify values for both regular and default arguments:

configure_device("Router2", configuration_mode="advanced")
# Output: Configuring Router2 in advanced mode.

Here, we specified both device_name and configuration_mode parameters, allowing flexibility in function calls.
When dealing with default arguments, you can choose to specify positional arguments first, followed by keyword argu-
ments. If values are passed without specifying their position, they will be assigned based on the order of parameters.

90 Chapter 8. Functions and Classes


Python for Network Engineer's, Release 2.0

8.8 Keyword (Named) Arguments

Sending arguments with the key=value syntax, known as keyword (named) arguments, provides a flexible way to call
networking functions where the order of arguments becomes independent.
Consider the following example with a function configure_network_device:

def configure_network_device(device_name, configuration_mode="basic", interface="eth0"):


print(f"Configuring {device_name} in {configuration_mode} mode on interface
˓→{interface}.")

configure_network_device("Router1", configuration_mode="advanced", interface="eth1")


# Output: Configuring Router1 in advanced mode on interface eth1.

In this scenario, the function configure_network_device has a regular argument device_name and two default
arguments, configuration_mode and interface. By using keyword arguments, the order of the arguments becomes
flexible. This allows you to specify values for particular parameters, making your function calls more explicit.
With keyword arguments, the positions of the arguments become less critical. However, it’s important to note that
keyword arguments should come after positional arguments and before default arguments in a function call. Here’s an
example:

configure_network_device("Router2", interface="eth2")
# Output: Configuring Router2 in basic mode on interface eth2.

In this case, we specified the device_name positionally and provided a value for the interface parameter using a
keyword argument.

8.9 *args and **kwargs in Functions

In functions, flexibility is key, and Python supports the concept of handling an arbitrary number of arguments and
keyword arguments through *args and **kwargs.
*args - An Arbitrary Number of Arguments

def network_status(*devices):
print("Devices in the network:", devices)

network_status("Router1", "Switch2", "Firewall3")


# Output: Devices in the network: ('Router1', 'Switch2', 'Firewall3')

With *args, the function network_status can accept an arbitrary number of devices, making it suitable for scenarios
where the number of devices in the network is not fixed.
**kwargs - An Arbitrary Number of Keyword Arguments

def configure_device(**config_params):
print("Configuration parameters:", config_params)

configure_device(device_type="Router", interface="eth0", vlan=10)


# Output: Configuration parameters: {'device_type': 'Router', 'interface': 'eth0', 'vlan': 10}

Using **kwargs, the function configure_device can handle an arbitrary number of keyword arguments, allowing
for a more dynamic configuration approach.

8.8. Keyword (Named) Arguments 91


Python for Network Engineer's, Release 2.0

8.10 Handling Tuple and Dictionary as Arguments

# Handling Tuple as Argument


my_tuple = (1, 2, 3)

def tup_output(*args):
print("Tuple output:", args)

tup_output(*my_tuple)
# Output: Tuple output: (1, 2, 3)

# Handling Dictionary as Keyword Argument


my_dict = {'one': 1, 'two': 2}

def dict_output(**kwargs):
print("Dictionary output:", kwargs)

dict_output(**my_dict)
# Output: Dictionary output: {'one': 1, 'two': 2}

Here, *my_tuple allows the function to extract individual values from the tuple, and **my_dict passes each key-value
pair as keyword arguments.
Understanding *args and **kwargs provides a powerful mechanism for creating adaptable and versatile networking
functions in Python.

8.11 Scope of Variables in function

In Python, the scope of variables is crucial to understand, as variables can be either local or global. Local variables are
declared within functions and are accessible only within those functions, while global variables are declared outside
of functions and are accessible throughout the entire script, including within functions.

8.11.1 Global Variables

Global variables are declared outside of any function in a Python script. They are accessible from anywhere in the
code, including functions within the program.

s = '5'

def num(n):
print("local:", n)
print("global:", s)

num(6)
# Output:
# local: 6
# global: 5

In this example, the variable s is declared globally and is accessible both within the function num and outside of it.

92 Chapter 8. Functions and Classes


Python for Network Engineer's, Release 2.0

8.11.2 Local Variables

Local variables are declared inside functions and are only accessible within the functions where they are defined. They
cannot be accessed outside of the function.

a = 5

def num():
b = 3
print(b)

num()
# Output: 3

print(a)
# Output: 5

Here, the variable a is global and can be accessed both inside and outside the function num. However, the variable b is
local to the function num and cannot be accessed outside of it.

# Attempting to access local variable b outside of the function


print(b)
# Output: NameError: name 'b' is not defined

If we attempt to access the local variable b outside of the function, it results in a NameError since b is not defined in
the global scope.
Understanding variable scope is crucial for writing modular and maintainable networking scripts in Python.
As a network engineer diving into Python, these fundamental concepts pave the way for effective network automation.
Functions, return values, arguments, and variable scope are the building blocks of modular, scalable, and maintainable
networking scripts. By embracing Python’s versatility, network engineers can streamline tasks, enhance efficiency, and
adapt to the dynamic nature of networking environments.

8.12 Classes and Objects

In Python, everything is an object, meaning everything we create in Python is associated with functions/methods or
attributes, or both, attached to an object. This is because everything in Python stems from a class. A class serves as a
blueprint for creating objects.

8.12.1 Creating a Class in Python

Let’s take the example of creating a blueprint for a car. A car has certain attributes (properties) and actions (methods).

8.12. Classes and Objects 93


Python for Network Engineer's, Release 2.0

Attributes

• Color
• Fuel type

Methods

• Type of car
• Capacity of car
To define a class in Python, we use the keyword class:

class Car:

# Constructor method
def __init__(self, color, fuel):
self.color = color
self.fuel = fuel

A class essentially outlines how something should be defined. It doesn’t contain actual data. All class definitions start
with the class keyword, followed by the name of the class and a colon. Any code indented below the class definition
is considered part of the class’s body.
Python class names conventionally use CapitalizedWords: MyClass or My_Class.

8.12.2 Objects/Instances

While a class is a blueprint, an instance is an object built from that blueprint, containing data. Once we create an
instance of the Car class, it’s no longer a blueprint:

# Create an object of the class


mike_car = Car("Red", "Petrol")
# Accessing object attributes
print(f"Color Type: {mike_car.color}")
print(f"Fuel Type: {mike_car.fuel}")

Color Type: Red


Fuel Type: Petrol

8.12.3 Instance Methods

Instance methods are functions defined inside a class, callable only from an instance of that class. The first parameter
of an instance method is always self.

class Car:

# Constructor method
def __init__(self, color, fuel):
self.color = color
self.fuel = fuel
(continues on next page)

94 Chapter 8. Functions and Classes


Python for Network Engineer's, Release 2.0

(continued from previous page)

# Instance method
def type(self):
description = f"Car has {self.color} color and fuel type is {self.fuel}."
return description

# Create an object of the class


mike_car = Car("Red", "Petrol")
# Call instance method
print(mike_car.type())

Car has Red color and fuel type is Petrol.

Let’s add more methods:

# Define a class named Car


class Car:

# Constructor method to initialize object attributes


def __init__(self, color, fuel):
self.color = color # Set the color attribute
self.fuel = fuel # Set the fuel attribute

# Method to describe the type of car


def type(self):
description = f"Car has {self.color} color and fuel type is {self.fuel}."
return description

# Create an object (instance) of the Car class


mike_car = Car("Red", "Petrol")

# Call the type() method to describe the car


print(mike_car.type())

# Define the Car class with more methods


class Car:

# Constructor method to initialize object attributes


def __init__(self, color, fuel):
self.color = color # Set the color attribute
self.fuel = fuel # Set the fuel attribute

# Method to set the owner of the car


def owner(self, name):
self.name = name # Set the name attribute
return f"Car owner name is {self.name}"

# Method to describe the type of car


def type(self):
description = f"and has {self.color} color, fuel type is {self.fuel}"
return description

(continues on next page)

8.12. Classes and Objects 95


Python for Network Engineer's, Release 2.0

(continued from previous page)


# Method to describe the capacity of the car
def capacity(self, num):
self.num = num # Set the num attribute
return f"and capacity is: {self.num}"

# Create an object (instance) of the Car class


mike = Car("Red", "Petrol")

# Call various methods to describe the car


car_owner = mike.owner("'Mike'")
car_type = mike.type()
car_cap = mike.capacity(3)

# Construct the complete description of the car


car_description = f"{car_owner} {car_type} {car_cap}"
print(car_description)

Car owner name is 'Mike' and has Red color, fuel type is Petrol and capacity is: 3

The topic of Object Oriented Programming is vast, and there’s much more to Python objects and OOP than we have
cover here.

96 Chapter 8. Functions and Classes


CHAPTER

NINE

NETWORK AUTOMATION LAB

One of the best ways to learn about a topic is to practice what you study, validate it, and always test your assumptions.
In my opinion, there’s nothing that can substitute the hands-on experience you get by reading the book, now to test and
configure your own network projects.

9.1 Setting Up a Network Lab with GNS3

Networking labs, providing a sandbox for testing, learning, and refining skills. In this blog, we’ll delve into the process
of setting up a virtual network lab using GNS3 on a Windows PC. This comprehensive guide will walk you through the
installation of GNS3 and its virtual machine (GNS3 VM) within VMware. Additionally, we’ll explore the integration
of an Ubuntu 22 Server, establishing a robust foundation for hands-on networking experimentation.
The key components of this setup include GNS3, a powerful network simulation tool, VMware for virtualization, and
an Ubuntu Server for practical application testing. By following the outlined steps, you’ll create a cohesive environment
that bridges your Windows host, GNS3, and Ubuntu Server, fostering seamless interaction.
This introductory section sets the stage for an insightful journey into the intricacies of building a dynamic virtual
network lab. As we progress through the subsequent sections, we’ll cover each step in detail, providing clarity and
guidance to empower you in constructing a robust and functional network testing environment. Whether you’re a
network enthusiast, a student, or a professional seeking hands-on experience, this blog aims to equip you with the
knowledge to effectively set up and utilize a virtual network lab on your Windows PC.

9.2 Prerequisites of Lab

Before embarking on the journey to set up your virtual network lab with GNS3 on Windows, it’s crucial to ensure that
you have all the necessary prerequisites in place. Here’s a quick rundown of the essential components you’ll need:

9.2.1 Windows PC Minimum Requirements

Ensure that you have a Windows PC that meets the system requirements for running GNS3 and VMware smoothly.
This includes adequate RAM, CPU, and disk space. See the website

97
Python for Network Engineer's, Release 2.0

9.2.2 GNS3 and GNS3 VM Installed in VMware

Make sure you have successfully installed GNS3 and its virtual machine (GNS3 VM) within VMware. You can down-
load GNS3 from the official website and follow the installation instructions. See this video and this video for installation.

9.2.3 Ubuntu 22 Server Installed in VMware

Install Ubuntu 22 Server as a virtual machine in VMware. This server will be an integral part of your network lab.
Ensure that the VM is configured with sufficient resources for optimal performance.
Now that you’ve confirmed the presence of these fundamental components, you’re ready to proceed to the next steps.

9.3 Starting the Lab Environment

With the prerequisites in place, it’s time to kick off the virtual network lab environment. This section outlines the steps
to start GNS3, launch the GNS3 VM in VMware, and initiate the Ubuntu 22 Server.

9.3.1 Start GNS3

Open GNS3 on your Windows PC. Navigate through any initial setup wizards or configurations if prompted. Once the
GNS3 interface is accessible, you’re ready to proceed.

9.3.2 Launch GNS3 VM in VMware

Ensure that VMware is installed on your Windows PC. Start VMware and locate the GNS3 VM in your virtual machine
library. Power on the GNS3 VM to establish the connection between GNS3 and the virtual machine.

9.3.3 Configuring Ubuntu Server

In this section, we’ll focus on configuring the Ubuntu 22 Server within your virtual lab environment. A crucial step in
this process is setting up a bridge network for the Ubuntu Server in VMware. Follow these steps to ensure seamless
connectivity:

9.3.4 Open VMware Settings for Ubuntu Server

1. In VMware, locate the Ubuntu 22 Server virtual machine.


2. Ensure the VM is powered off.
3. Right-click on the VM and select “Settings.”

98 Chapter 9. Network Automation Lab


Python for Network Engineer's, Release 2.0

9.3.5 Configure Network Adapter to Bridge Mode

1. In the VM Settings window, navigate to the “Network Adapter” tab.


2. Change the network connection mode to “Bridged.”
3. Select the network adapter connected to your physical network.
4. Click “OK” to save the changes.

Fig. 1: VMWare Setting

9.3.6 Start Ubuntu Server

1. Power on the Ubuntu 22 Server virtual machine in VMware.


2. Allow the VM to boot up and log in using the credentials you configured during the Ubuntu setup.

9.3.7 Verify Network Configuration

1. Open a terminal in Ubuntu.


2. Use the following command to check the network interfaces:

ip a

1. Ensure that the network interface is associated with an IP address and is in the “UP” state.
By configuring a bridge network, you enable direct communication between the Ubuntu Server and your physical
network, facilitating connectivity with other devices in the virtual lab. With the network settings in place, proceed to
Section 4, where we guide you through adding a cloud node to GNS3 and selecting your network.

9.4 Adding Cloud Node to GNS3

In this section, we’ll expand your virtual network lab in GNS3 by adding a cloud node. The cloud node serves as a
bridge between the GNS3 environment and your physical network, enabling connectivity to external devices. Follow
these steps to seamlessly integrate the cloud node:

9.4. Adding Cloud Node to GNS3 99


Python for Network Engineer's, Release 2.0

9.4.1 Access the GNS3 Workspace

1. In the GNS3 interface, navigate to the workspace where you plan to build your network topology.

9.4.2 Add Cloud Node

1. From the GNS3 toolbar, locate and drag the “Cloud” node into the workspace.
2. Connect the cloud node to your existing devices using appropriate link types (Ethernet, Wifi).

9.4.3 Configure Cloud Node

1. Right-click on the cloud node and select “Configure.”


2. In the “Node Configurations” window, select the “Ethernet Interface” tab.
3. Choose the network adapter that corresponds to your physical network.

Fig. 2: Cloud Node

9.4.4 Apply Changes

1. Click “OK” to apply the changes to the cloud node.


2. Save the GNS3 project to preserve the current configuration.
By adding a cloud node and configuring it to use your physical network, you establish a vital link between GNS3 and
external devices. This integration sets the stage for comprehensive testing and interaction within your virtual network
lab.

100 Chapter 9. Network Automation Lab


Python for Network Engineer's, Release 2.0

9.5 Installing VSCode and SSH Extension

In this section, we’ll enhance your virtual network lab environment by integrating Visual Studio Code (VSCode) and
installing the SSH extension. VSCode provides a versatile platform for code development and, with the SSH extension,
allows seamless interaction with your Ubuntu Server. Follow these steps to set up this essential coding and remote
connection environment:

9.5.1 Download and Install VSCode

1. Visit the official VSCode website at VSCode Downloads to download the installer.
2. Run the installer and follow the on-screen instructions to complete the installation.

9.5.2 Open VSCode

Once VSCode is installed, open the application on your Windows PC.

9.5.3 Install SSH Extension

1. In VSCode, go to the Extensions by clicking on the Extensions icon in the Activity Bar on the side of the window
or using the shortcut Ctrl+Shift+X.
2. Search for “Remote - SSH” in the Extensions view search box.
3. Click “Install” next to the “Remote - SSH” extension.

Fig. 3: VS Code

9.5.4 Configure SSH Connection

In the bottom-left corner of the VSCode window, click on the blue square icon (Remote Explorer). Click on the
“Connect to Host” to add a new SSH target. Enter the SSH connection details for your Ubuntu Server (username, IP
address).
With VSCode and the SSH extension installed, you now have a powerful coding environment directly linked to your
Ubuntu Server. This integration streamlines the process of writing and testing Python scripts for your network lab.

9.5. Installing VSCode and SSH Extension 101


Python for Network Engineer's, Release 2.0

Fig. 4: SSH Connection

9.6 Connecting VSCode to Ubuntu Server

In this section, we’ll establish a direct connection between Visual Studio Code (VSCode) and your Ubuntu Server
within the GNS3 virtual network lab. This connection allows you to seamlessly write, test, and execute Python scripts
on the Ubuntu Server. Follow these steps to ensure a smooth integration:

9.6.1 Access Remote Explorer

1. In the bottom-left corner of the VSCode window, click on the square icon (Remote Explorer).
2. You should see the SSH target you added in the previous section.

9.6.2 Connect to Ubuntu Server

1. Click on the SSH target representing your Ubuntu Server.


2. VSCode will establish an SSH connection to the Ubuntu Server, and you’ll be prompted for the password. Enter
the password for your Ubuntu Server.

9.6.3 Verify Connection

1. Once connected, you should see the Ubuntu Server’s file system in the VSCode Explorer.
2. Open a terminal in VSCode and run basic commands to verify the SSH connection.

102 Chapter 9. Network Automation Lab


Python for Network Engineer's, Release 2.0

9.6.4 Create and Test Python Scripts

1. In VSCode, create a new Python file.


2. Write a simple Python script (e.g., print(“Hello, GNS3!”)) and save the file.
3. Use the VSCode terminal to navigate to the directory containing your Python script and run it.
With this connection established, you can seamlessly develop, debug, and test Python scripts directly on your Ubuntu
Server from within the VSCode environment. This integration empowers you to leverage the full potential of your
virtual network lab for scripting and automation tasks.

9.7 Finalizing the Lab Setup

Congratulations on successfully setting up your virtual network lab with GNS3, Ubuntu Server, and Visual Studio
Code (VSCode). In this section, we’ll recap the key components of your lab setup and highlight the benefits of the
interconnected environment you’ve created.

9.7.1 Recap of Components

Let’s review the core elements of your virtual network lab:


• GNS3: The network simulation tool providing a platform to design and test complex network topologies.
• Ubuntu Server: A virtual machine running in VMware, serving as a network device for practical testing and
application deployment.
• Visual Studio Code (VSCode): Your coding environment with the added capability of connecting directly to
the Ubuntu Server for Python script development.

9.7.2 Achieved Connections

Through the steps outlined in this guide, you’ve achieved the following connections:
• GNS3 and Ubuntu Server: GNS3 is integrated with the Ubuntu Server, allowing for dynamic interactions and
testing within the simulated network environment.
• Windows PC and GNS3: Your Windows host is the control center for GNS3, facilitating topology design,
configuration, and monitoring.
• VSCode and Ubuntu Server: VSCode is directly connected to the Ubuntu Server via SSH, enabling seamless
coding, script execution, and testing.

9.7.3 Benefits of the Lab Setup

By creating this integrated virtual network lab, you’ve gained several advantages:
• Hands-on Learning: Actively engage with networking concepts through practical exercises and experiments.
• Scripting and Automation: Leverage Python scripts to automate network configurations and tasks.
• Real-world Simulation: Mimic real-world network scenarios and challenges within the GNS3 environment.

9.7. Finalizing the Lab Setup 103


Python for Network Engineer's, Release 2.0

9.7.4 Next Steps

As you continue your network exploration, consider the following next steps:
• Expand Your Topology: Add more devices to your GNS3 topology to simulate complex network architectures.
• Experiment with Python: Explore advanced Python scripts to handle various networking tasks.
• Document Your Lab: Maintain comprehensive documentation of your lab setup, including configurations and
scripts, for future reference.

9.8 Conclusion

In conclusion, you’ve successfully created a virtual network lab that serves as an invaluable resource for testing Python
code on networking devices. This hands-on environment, comprising GNS3, Ubuntu Server, and VSCode, empowers
you to refine your networking and coding skills in a dynamic and controlled setting.

104 Chapter 9. Network Automation Lab


CHAPTER

TEN

TELNET PROGRAMMING IN PYTHON: STREAMLINING NETWORK


OPERATIONS

Telnet, an abbreviation for Telecommunication Network, stands out as a protocol that facilitates text-based communi-
cation sessions with remote devices over a network. Operating at the application layer of the OSI model, Telnet proves
invaluable for accessing and managing network devices. In contrast to graphical interfaces, Telnet relies on plain text
communication, offering a lightweight and versatile option for various networking scenarios.

10.1 Understanding Telnet’s Operation

Telnet operates on a client-server model where the client initiates a connection to the server. Once connected, the client
can send commands, and the server responds with the output, typically in ASCII format, making it human-readable.

10.2 Importance in Network Programming

10.2.1 Empowering Network Automation

Telnet plays a pivotal role in network automation by enabling the scripting and automation of tasks related to configuring
and managing network devices. Network engineers leverage scripts to automate repetitive tasks, reducing manual
intervention and enhancing overall efficiency. Telnet facilitates the programmatic configuration of network devices,
enabling standardized and consistent setups.
The simplicity and ease of use of Telnet make it an attractive option for programmers seeking to interact with network
devices programmatically.

10.3 Integration with Python

Python, a powerful and widely used programming language, provides the telnetlib module, facilitating seamless
integration of Telnet functionality into Python scripts. The telnetlib module simplifies the implementation of Telnet
communication, offering a set of functions for establishing connections, sending commands, and handling responses.

105
Python for Network Engineer's, Release 2.0

10.4 Basics of Telnetlib

10.4.1 Setting Up telnetlib

Before delving into Telnet programming with Python, it’s crucial to ensure that the telnetlib module is available.
Fortunately, for most Python installations, telnetlib is included in the standard library, requiring no separate instal-
lation.
To start using telnetlib in Python scripts, it must be imported. The following code snippet demonstrates how to
import the telnetlib module:
import telnetlib

Establishing a Telnet Connection:


tn = telnetlib.Telnet("172.16.10.12", 23) # IP and Port
tn.write(b"admin\n") # Username
tn.write(b"cisco\n") # Password

telnetlib provides methods for handling authentication, such as read_until to wait for a specific string and write
to send login credentials, which is crucial for accessing network devices programmatically.
Sending Commands:
command = "show ip interface brief"
tn.write(command.encode("ascii") + b"\n")
tn.write(b"exit\n")
print(tn.read_all().decode("ascii"))

Reading and handling responses from the device is done using the read_until method, which waits for a specified
string to be received. This is crucial for capturing the output of executed commands.
Example: Automating VLAN Configuration on Switches
import getpass
import telnetlib

# User input for IP, username, and password


IP = input("Enter IP Address: ")
user = input("Enter your username: ")
password = getpass.getpass()

# Telnet connection and authentication


tn = telnetlib.Telnet(IP)
tn.read_until(b"Username: ")
tn.write(user.encode("ascii") + b"\n")
if password:
tn.read_until(b"Password: ")
tn.write(password.encode("ascii") + b"\n")

# VLAN configuration commands


tn.write(b"enable\n")
tn.write(b"cisco\n")
tn.write(b"conf t\n")

(continues on next page)

106 Chapter 10. Telnet Programming in Python: Streamlining Network Operations


Python for Network Engineer's, Release 2.0

(continued from previous page)


for n in range(2, 5):
tn.write(b"vlan " + str(n).encode("ascii") + b"\n")
tn.write(b"name VLAN_" + str(n).encode("ascii") + b"\n")

tn.write(b"end\n")
tn.write(b"show vlan br\n\n")
tn.write(b"exit\n")
print(tn.read_all().decode("ascii"))

This example illustrates how Telnet programming in Python can be applied to automate network configuration tasks,
enhancing efficiency and reducing the risk of manual errors.

10.5 Security Considerations

In the real world, Telnet has historically been widely used for remote management and troubleshooting of network
devices. However, it’s essential to note that Telnet is considered less secure compared to modern alternatives like SSH
(Secure Shell), and its use in sensitive environments is discouraged due to the lack of encryption. Despite this, there
are situations where Telnet continues to be employed:
1. Legacy Systems: In some legacy systems and environments, Telnet might still be in use due to historical reasons.
Migrating away from Telnet could be a complex process, and organizations may continue using it until a more
secure solution is implemented.
2. Internal Networks: Within closed, internal networks where security concerns are minimal, Telnet might be
used for simplicity and ease of configuration. However, even in such cases, there is a growing trend to adopt
more secure protocols.
3. Non-sensitive Applications: Telnet might be suitable for non-sensitive applications or scenarios where encryp-
tion is not a critical requirement. For example, in isolated lab environments or network testing setups.
4. Quick Troubleshooting: Network administrators might use Telnet for quick troubleshooting, especially when
dealing with non-sensitive information. It allows them to connect to network devices and perform basic checks
without the overhead of encryption.
5. Education and Learning: Telnet is sometimes used in educational settings to introduce students to basic net-
working concepts. It provides a simple and accessible way to demonstrate communication between devices.
6. Scripting and Automation: Telnet, along with telnetlib in Python or similar libraries in other languages, is
still used for scripting and automation in scenarios where encryption is not a primary concern. This can include
automating interactions with devices for configuration purposes.
7. Interoperability Testing: In certain cases, for interoperability testing or when dealing with specific devices that
only support Telnet, it might be used as a temporary solution until more secure alternatives are available.
It’s essential to be aware of the security risks associated with Telnet, especially in environments where sensitive infor-
mation is transmitted. As network technologies advance, there is a general shift towards adopting more secure protocols
like SSH for remote management and communication.
For more examples and code snippets, check out my GitHub repository on network programming with Telnet in Python.
Feel free to explore additional scripts and contribute to the collection!

10.5. Security Considerations 107


Python for Network Engineer's, Release 2.0

108 Chapter 10. Telnet Programming in Python: Streamlining Network Operations


CHAPTER

ELEVEN

PARAMIKO - SECURE SSH CONNECTIONS IN PYTHON

Paramiko is a Python library that provides a pure-Python implementation of SSHv2 for secure communication be-
tween Python applications and remote devices over SSH. Its primary purpose is to facilitate secure remote access and
management of network devices, servers, and other systems.
Paramiko offers a range of capabilities essential for building robust and secure SSH connections in Python. These
capabilities include:
• Establishing encrypted SSH connections
• Authenticating users using various methods (e.g., passwords, SSH keys)
• Executing commands on remote hosts and retrieving responses
• Transferring files securely between hosts using SFTP
• Handling SSH negotiation, key exchange, and encryption algorithms

11.1 Setting up Paramiko for SSH Communication

Before using Paramiko in Python scripts, it’s essential to install the library and set up the development environment.
Paramiko can be installed via pip, the Python package manager, using the following command:

pip install paramiko

Once installed, developers can import the Paramiko module in their Python scripts and start using its classes and
functions to establish SSH connections and interact with remote devices.

11.2 Establishing Secure Connections to Network Devices

Paramiko allows developers to establish secure SSH connections to network devices, servers, and other systems. The
process involves creating a Paramiko SSH client object, specifying connection parameters such as the hostname, port,
and authentication credentials, and then initiating the connection.

import paramiko

# Create SSH client


client = paramiko.SSHClient()

# Set policy to automatically add hosts to known hosts file


client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
(continues on next page)

109
Python for Network Engineer's, Release 2.0

(continued from previous page)

# Connect to the remote host


client.connect(
hostname="172.16.10.12",
username="admin",
password="cisco",
look_for_keys=False,
allow_agent=False,
)

# Perform operations on the remote host

# Close the connection


client.close()

11.3 Executing Commands and Handling Responses Asyn-


chronously

Once a secure SSH connection is established, developers can execute commands on the remote host and handle the
command output asynchronously using Paramiko. This allows for efficient execution of multiple commands and parallel
processing of command responses.
import paramiko
import time

# Create SSH client


client = paramiko.SSHClient()

# Set policy to automatically add hosts to known hosts file


client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Connect to the remote host


client.connect(
hostname="172.16.10.12",
username="admin",
password="cisco",
look_for_keys=False,
allow_agent=False,
)

# Open an interactive SSH session


ssh_client = client.invoke_shell()

# Send command
ssh_client.send("sh ip int bri\n")

# Wait for the command to be finished


time.sleep(3)

# Receive and process command output


(continues on next page)

110 Chapter 11. Paramiko - Secure SSH Connections in Python


Python for Network Engineer's, Release 2.0

(continued from previous page)


output = ssh_client.recv(65000)
print(output.decode("ascii"))

# Close the SSH session


ssh_client.close()

# Close the connection


client.close()

R1#sh ip int bri


Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 172.16.10.12 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM administratively down down
R1#

11.4 Handling Exceptions and Error Scenarios Gracefully

In network automation and remote system management, it’s crucial to handle exceptions and error scenarios gracefully
to ensure the robustness and reliability of the application. Paramiko provides mechanisms for catching and handling
exceptions that may occur during SSH communication, such as authentication errors, connection timeouts, and network
failures.
import paramiko
import time

# Create SSH client


try:

client = paramiko.SSHClient()

# Set policy to automatically add hosts to known hosts file


client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Connect to the remote host


client.connect(
hostname="172.16.10.12",
username="admin",
password="cisco",
look_for_keys=False,
allow_agent=False,
)

# Open an interactive SSH session


ssh_client = client.invoke_shell()

# Send command
ssh_client.send("sh ip int bri\n")

# Wait for the command to be finished


(continues on next page)

11.4. Handling Exceptions and Error Scenarios Gracefully 111


Python for Network Engineer's, Release 2.0

(continued from previous page)


time.sleep(3)

# Receive and process command output


output = ssh_client.recv(65000)
print(output.decode("ascii"))

# Close the SSH session


ssh_client.close()

# Close the connection


client.close()

except paramiko.AuthenticationException:
print("Authentication failed. Please verify your credentials.")

By effectively handling exceptions and error scenarios, developers can build resilient and fault-tolerant Python appli-
cations for secure SSH communication using Paramiko.

11.5 Advanced Example: Creating Loopback Interfaces

In the next example, we employ a for loop and the range() function to create loopback interfaces on a Cisco router.
import paramiko
import time

# Create an SSH client instance


client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Connect to the remote device


client.connect(
hostname="172.16.10.12",
username="admin",
password="cisco",
look_for_keys=False,
allow_agent=False,
)

# Start an interactive shell


ssh_client = client.invoke_shell()
print("###### Creating loopback interfaces ######")

# ssh_client.send("cisco\n")
ssh_client.send("conf ter\n")

# Use a for loop and range() to create loopback interfaces


for interface_number in range(0, 1):
ssh_client.send(f"int lo {interface_number}\n")
ssh_client.send(f"ip address 1.1.1.{interface_number} 255.255.255.255\n")

# Wait for the configurations to take effect


(continues on next page)

112 Chapter 11. Paramiko - Secure SSH Connections in Python


Python for Network Engineer's, Release 2.0

(continued from previous page)


time.sleep(1)

ssh_client.send("end\n")
ssh_client.send("show ip int brief\n")

# Wait for the output and retrieve it


time.sleep(3)
output = ssh_client.recv(65000)
print(output.decode("ascii"))

# Close the SSH connection


client.close()

###### Creating loopback interfaces ######

R1#conf ter
Enter configuration commands, one per line. End with CNTL/Z.
R1(config)#int lo 0
R1(config-if)#ip address 1.1.1.0 255.255.255.255
R1(config-if)#end
R1#show ip int brief
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 172.16.10.12 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM administratively down down
Loopback0 1.1.1.0 YES manual up up
R1#

This advanced example demonstrates the flexibility of Paramiko for configuring network devices programmatically.
The script showcases the creation of loopback interfaces, providing a practical illustration of how automation can
simplify repetitive tasks.

11.6 Connecting to Multiple Devices

Expanding our horizons, the following example demonstrates using a for loop to connect to multiple devices sequen-
tially.

import paramiko
import time

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# For loop and range() function to connect to multiple devices


for device in range(11, 12):
host = "172.16.10." + str(device)
print("\n##### Connecting to the device " + host + " #####")

client.connect(
hostname=host,
username="admin",
(continues on next page)

11.6. Connecting to Multiple Devices 113


Python for Network Engineer's, Release 2.0

(continued from previous page)


password="cisco",
look_for_keys=False,
allow_agent=False,
)

ssh_client = client.invoke_shell()
ssh_client.send("show ip int brief\n")
time.sleep(3)
output = ssh_client.recv(65000)
print(output.decode("ascii"))
client.close()

##### Connecting to the device 172.16.10.11 #####

**************************************************************************
* IOSv is strictly limited to use for evaluation, demonstration and IOS *
* education. IOSv is provided as-is and is not supported by Cisco's *
* Technical Advisory Center. Any use or disclosure, in whole or in part, *
* of the IOSv Software or Documentation to any third party for any *
* purposes is expressly prohibited except as otherwise authorized by *
* Cisco in writing. *
**************************************************************************
SW1#show ip int brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 unassigned YES unset up up
GigabitEthernet0/1 unassigned YES unset up up
GigabitEthernet0/2 unassigned YES unset down down
GigabitEthernet0/3 unassigned YES unset down down
GigabitEthernet1/0 unassigned YES unset down down
GigabitEthernet1/1 unassigned YES unset down down
GigabitEthernet1/2 unassigned YES unset down down
GigabitEthernet1/3 unassigned YES unset down down
GigabitEthernet2/0 unassigned YES unset down down
GigabitEthernet2/1 unassigned YES unset down down
GigabitEthernet2/2 unassigned YES unset down down
GigabitEthernet2/3 unassigned YES unset down down
GigabitEthernet3/0 unassigned YES unset down down
GigabitEthernet3/1 unassigned YES unset down down
GigabitEthernet3/2 unassigned YES unset down down
GigabitEthernet3/3 unassigned YES unset down down
Loopback0 1.1.1.0 YES manual up up
Vlan1 172.16.10.11 YES NVRAM up up
SW1#

##### Connecting to the device 172.16.10.12 #####

R1#show ip int brief


Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 172.16.10.12 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM administratively down down
Loopback0 1.1.1.0 YES manual up up
R1#

114 Chapter 11. Paramiko - Secure SSH Connections in Python


Python for Network Engineer's, Release 2.0

This script demonstrates the scalability of Paramiko, allowing network engineers to connect and interact with multiple
devices seamlessly.

11.7 Connecting to Multiple Devices with a List

In this example, we use a for loop and a list to connect to multiple devices. This approach provides greater flexibility
and ease of maintenance.

import paramiko
import time

username = "admin" # username


password = "cisco" # password

# IP list for network devices


devices = ["172.16.10.11", "172.16.10.12"]

# For loop and list to connect to multiple devices


for device in devices:
print("\n #### Connecting to the device " + device + " ####\n")

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

client.connect(
hostname=device,
port=22,
username=username,
password=password,
look_for_keys=False,
allow_agent=False,
)

ssh_client = client.invoke_shell()
ssh_client.send("config ter\n")

# For loop and range() function to create loop back interface


for num in range(2, 5):
ssh_client.send("int loop " + str(num) + "\n")
ssh_client.send("ip address 1.1.1." + str(num) + " 255.255.255.255\n")

time.sleep(1)
ssh_client.send("end\n")
# ssh_client.send("term length 0\n")
ssh_client.send("show ip int brief\n")
time.sleep(3)
output = ssh_client.recv(65000)
print(output.decode("ascii"))

client.close()

11.7. Connecting to Multiple Devices with a List 115


Python for Network Engineer's, Release 2.0

# #### Connecting to the device 172.16.10.11 ####

# **************************************************************************
# * IOSv is strictly limited to use for evaluation, demonstration and IOS *
# * education. IOSv is provided as-is and is not supported by Cisco's *
# * Technical Advisory Center. Any use or disclosure, in whole or in part, *
# * of the IOSv Software or Documentation to any third party for any *
# * purposes is expressly prohibited except as otherwise authorized by *
# * Cisco in writing. *
# **************************************************************************
SW1#config ter
Enter configuration commands, one per line. End with CNTL/Z.
SW1(config)#int loop 2
SW1(config-if)#ip address 1.1.1.2 255.255.255.255
SW1(config-if)#int loop 3
SW1(config-if)#ip address 1.1.1.3 255.255.255.255
SW1(config-if)#int loop 4
SW1(config-if)#ip address 1.1.1.4 255.255.255.255
SW1(config-if)#end
SW1#show ip int brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 unassigned YES unset up up
GigabitEthernet0/1 unassigned YES unset up up
GigabitEthernet0/2 unassigned YES unset down down
GigabitEthernet0/3 unassigned YES unset down down
GigabitEthernet1/0 unassigned YES unset down down
GigabitEthernet1/1 unassigned YES unset down down
GigabitEthernet1/2 unassigned YES unset down down
GigabitEthernet1/3 unassigned YES unset down down
GigabitEthernet2/0 unassigned YES unset down down
GigabitEthernet2/1 unassigned YES unset down down
GigabitEthernet2/2 unassigned YES unset down down
GigabitEthernet2/3 unassigned YES unset down down
GigabitEthernet3/0 unassigned YES unset down down
GigabitEthernet3/1 unassigned YES unset down down
GigabitEthernet3/2 unassigned YES unset down down
GigabitEthernet3/3 unassigned YES unset down down
Loopback0 1.1.1.0 YES manual up up
Loopback2 1.1.1.2 YES manual up up
Loopback3 1.1.1.3 YES manual up up
Loopback4 1.1.1.4 YES manual up up
Vlan1 172.16.10.11 YES NVRAM up up
SW1#

#### Connecting to the device 172.16.10.12 ####

R1#config ter
Enter configuration commands, one per line. End with CNTL/Z.
R1(config)#int loop 2
R1(config-if)#ip address 1.1.1.2 255.255.255.255
R1(config-if)#int loop 3
(continues on next page)

116 Chapter 11. Paramiko - Secure SSH Connections in Python


Python for Network Engineer's, Release 2.0

(continued from previous page)


R1(config-if)#ip address 1.1.1.3 255.255.255.255
R1(config-if)#int loop 4
R1(config-if)#ip address 1.1.1.4 255.255.255.255
R1(config-if)#end
R1#show ip int brief
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 172.16.10.12 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM administratively down down
Loopback0 1.1.1.0 YES manual up up
Loopback2 1.1.1.2 YES manual up up
Loopback3 1.1.1.3 YES manual up up
Loopback4 1.1.1.4 YES manual up up
R1#

This script introduces a more modular approach, where devices are managed through a list, providing simplicity and
ease of maintenance.

11.8 Conclusion

Paramiko plays a pivotal role in network automation, empowering network engineers to streamline and automate various
tasks efficiently. By leveraging Paramiko for secure SSH communication with networking devices, network engineers
can enhance network operations’ efficiency and reliability.
The examples provided serve as foundational knowledge for creating customized automation workflows tailored to
specific network requirements. Embrace the power of Paramiko and Python to adapt and thrive in the dynamic landscape
of network management.

11.8. Conclusion 117


Python for Network Engineer's, Release 2.0

118 Chapter 11. Paramiko - Secure SSH Connections in Python


CHAPTER

TWELVE

SIMPLIFYING NETWORK DEVICE MANAGEMENT WITH NETMIKO

Netmiko is a powerful Python library designed to simplify SSH management of network devices. It builds upon the
Paramiko SSH library and provides a uniform API for interacting with a wide range of network devices. In this guide,
we’ll explore the key features of Netmiko and demonstrate how to use it for tasks such as executing commands, making
configuration changes, handling exceptions, and backing up device configurations.

12.1 What is Netmiko?

Netmiko streamlines SSH management to network devices by providing a unified interface for interacting with devices
from different vendors. Its main purposes include:
• Establishing SSH connections to devices
• Executing, retrieving, and formatting show commands
• Sending configuration commands

12.2 Installing Netmiko

Netmiko is not part of Python standard library, so you will have to install it using pip. The process is similar for all
operating systems such as macOS/Linux. To install Netmiko, use pip as below:

pip install netmiko

Netmiko has several requirements, which pip will automatically install for you, including Paramiko, scp, pyserial,
and textfsm.
To ensure Netmiko is correctly installed, you can test by importing it in Python shell.

12.3 Connecting a Single Device

To establish an SSH connection to a device using Netmiko, import the ConnectHandler class and provide the neces-
sary device details:

from netmiko import ConnectHandler

connection = ConnectHandler(
host="172.16.10.12", username="admin", password="cisco", device_type="cisco_ios"
)
(continues on next page)

119
Python for Network Engineer's, Release 2.0

(continued from previous page)


output = connection.send_command("show ip interface brief")
print(output)
connection.disconnect()

Interface IP-Address OK? Method Status Protocol


FastEthernet0/0 172.16.10.12 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM administratively down down
Loopback0 1.1.1.0 YES manual up up
Loopback2 1.1.1.2 YES manual up up
Loopback3 1.1.1.3 YES manual up up
Loopback4 1.1.1.4 YES manual up up

• Import the ConnectHandler class from Netmiko.


• Define device details including device type, IP address, username, password, and secret (if required).
• Use the ConnectHandler method to establish an SSH connection to the device.
• Send a show command show ip int brief to the device and print the output.
• Ensure to disconnect the SSH session by using the disconnect() method
Note: Ensure that the cisco device is correctly configured for SSH access and that the Python workstation
can successfully SSH into the device.

12.4 Simplify Device Connections with Python Dictionaries in Net-


miko

To keep our device inventory organized and accessible, Python dictionaries offer a well-ordered solution for this. Let’s
say we have a Cisco device with particulars such as: it’s running iOS, IP address, and a username and password to
access it.
We can pack all these details neatly into a Python dictionary called SW_01:

SW_01 = {
"device_type": "cisco_ios",
"host": "172.16.10.12",
"username": "admin",
"password": "cisco"
}

Now, when we want to connect to this device using Netmiko, we simply unpack this dictionary:

from netmiko import ConnectHandler

# Unpacking the dictionary to connect to the device


connection = ConnectHandler(**SW_01)

We’ve established a connection to our device using the credintials stored in our dictionary. Next, we can send commands
to our device as usual. Let’s fetch the descriptions of its interfaces:

output = connection.send_command('show interface desc')


print(output)

120 Chapter 12. Simplifying Network Device Management with Netmiko


Python for Network Engineer's, Release 2.0

And when we’re done, we can gracefully close the connection:

connection.disconnect()

Interface Status Protocol Description


Fa0/0 up up
Fa0/1 admin down down

Using dictionaries for device details not only keeps our code organized but also allows us to reuse this information
throughout our script. It’s like having all your tools neatly arranged in a toolbox, ready for use whenever you need
them.

12.5 Enabling Privilege EXEC Mode with Netmiko

When we’re automating tasks on network devices using Netmiko, sometimes we encounter situations where our default
login mode doesn’t grant us the necessary permissions. For instance, trying to run certain commands like show run
might result in errors.
To tackle this, Netmiko offers a solution: enabling Privilege EXEC mode. This mode grants us elevated privileges,
allowing us to execute a wider range of commands. Let’s see how we can do this in a simple and straightforward
manner.
First, we define our device details in a Python dictionary, just like before:

SW_01 = {
"device_type": "cisco_ios",
"host": "172.16.10.12",
"username": "admin",
"password": "cisco",
"secret": "cisco123" # Enable password
}

Notice the addition of the secret parameter. This is where we specify our enable password, which is needed to
access Privilege EXEC mode. Next, we establish a connection to our device as usual:

connection = ConnectHandler(**SW_01)

Now, here comes the helpful function! The enable() method provided by Netmiko to switch to Privilege EXEC mode:

connection.enable()

This simple line of code elevates our permissions, giving us access to more powerful commands. To verify that we’ve
successfully switched to Privilege EXEC mode, we can use the find_prompt() method:

device_prompt = connection.find_prompt()
print(device_prompt)

This will print out the prompt of our device, confirming that we’re now in Privilege EXEC mode. Now, we can
confidently execute commands like show run without encountering permission issues:

output = connection.send_command('show run')


print(output)

And when we’re done, it’s good practice to gracefully close the connection:

12.5. Enabling Privilege EXEC Mode with Netmiko 121


Python for Network Engineer's, Release 2.0

connection.disconnect()

With just a few lines of code, we’ve unlocked the full potential of our network automation script by enabling Privilege
EXEC mode with Netmiko.

12.6 Device Configuration with Netmiko

Netmiko, offers a seamless way to enter Global Configuration Mode, where we can make changes to our device’s
settings. Let’s dive into how we can tackle the power of Global Configuration Mode using Netmiko.
First, we set up our device details in a Python dictionary, just like before:

SW_01 = {
"device_type": "cisco_ios",
"host": "172.16.10.11",
"username": "admin",
"password": "cisco",
"secret": "cisco123" # Enable password
}

We then establish a connection to our device and elevate our permissions to Privilege EXEC mode:

connection = ConnectHandler(**SW_01)
connection.enable() # Enable method

Now, it’s time to enter Global Configuration Mode using the config_mode() method:

connection.config_mode() # Global config mode

With Global Configuration Mode activated, we can execute configuration commands on our device. For example, let’s
create an ACL access-list 1 permit any:

connection.send_command('access-list 1 permit any')

Once we’re done with our configuration tasks, it’s important to exit Global Configuration Mode using the
exit_config_mode() method:

connection.exit_config_mode() # Exit global config mode

And just like that, we’ve seamlessly transitioned back to Privilege EXEC mode, ready to execute show commands or
perform other tasks.
As a demonstration, let’s fetch the descriptions of our device’s interfaces:

show_output = connection.send_command('show interface desc')


print(show_output)

Finally, as a best practice, we gracefully close the connection:

connection.disconnect()

With Netmiko’s Global Configuration Mode, configuring devices becomes as easy as pie. Whether it’s creating ACLs,
adjusting interface settings, or making other configuration changes, Netmiko empowers us to automate with confidence.

122 Chapter 12. Simplifying Network Device Management with Netmiko


Python for Network Engineer's, Release 2.0

12.7 Safeguarding Passwords in Network Automation with Python’s


getpass

In the world of network automation, security is paramount. Storing passwords or secrets as plain text is a big mistake.
Fortunately, Python provides us with a solution: the getpass library, getpass allows us to prompt the user for sensitive
information, such as passwords, without echoing their input to the terminal. This means the password remains hidden
from view, enhancing security.
Let’s explore how we can use getpass to securely handle passwords in our network automation scripts.

from netmiko import ConnectHandler


import getpass

passwd = getpass.getpass('Please enter the password: ') # Prompt user for password␣
˓→securely

SW_01 = {
"device_type": "cisco_ios",
"host": "172.16.10.11",
"username": "admin",
"password": passwd, # Log in password from getpass
"secret": passwd # Enable password from getpass
}

connection = ConnectHandler(**SW_01)
connection.enable() # Enter Privilege EXEC mode

output = connection.send_command('show interface desc')


print(output)

connection.disconnect()

In this script, we use getpass to prompt the user for the password interactively. The entered password is then securely
saved as a string in the passwd variable.
Next, we define our device details, including the username and passwords obtained from getpass. These details are
then used to establish a connection to the device.
Once connected, we can execute commands on the device as needed. In this example, we fetch the descriptions of the
interfaces using the send_command method.
Finally, we gracefully close the connection to the device.
By leveraging getpass, we ensure that passwords are handled securely in our network automation scripts. No more
worries about storing sensitive information in plain text files!

12.7. Safeguarding Passwords in Network Automation with Python’s getpass 123


Python for Network Engineer's, Release 2.0

12.8 Sending Multiple Commands

When it comes to network automation, sending a single command to a single device is just the tip of the iceberg. What
we really want is the ability to send multiple commands, a mix of show and configuration commands, using a single
Python script. Thankfully, Netmiko makes this possible with its powerful send_config_set method.
Let’s delve into how we can leverage this feature to streamline our configuration tasks.
First, let’s set the stage by gathering the necessary details to connect to our device. We’ll prompt the user to enter the
password securely:

from netmiko import ConnectHandler


import getpass

passwd = getpass.getpass('Please enter the password: ')

SW_01 = {
"device_type": "cisco_ios",
"host": "172.16.10.11",
"username": "admin",
"password": passwd, # Log in password from getpass
"secret": passwd # Enable password from getpass
}

With our device details in place, we establish a connection and elevate our permissions to Privilege EXEC mode:

connection = ConnectHandler(**SW_01)
connection.enable()

Now, let’s define a list of configuration commands that we want to push to the device:

config_commands = ['interface gi0/0', 'description WAN', 'exit', 'access-list 1 permit␣


˓→any']

Using the send_config_set method, we can send this list of commands to the device. This method automatically
enters Global Configuration Mode, executes the commands, and then exits Global Configuration Mode:

connection.send_config_set(config_commands)

And just like that, we’ve pushed multiple configuration commands to our device with a single Python script. No need
to manually enter each command one by one.
To verify that our configurations have been applied, we can run show commands on the device:

print(connection.send_command('show interfaces description'))


print(connection.send_command('show access-lists'))

Finally, as we wrap up, let’s gracefully close the connection:

connection.disconnect()

With Netmiko’s send_config_set method, configuring devices becomes a breeze. Whether it’s setting descriptions
on interfaces, creating ACLs, or making other configuration changes, Netmiko empowers us to automate with ease.

124 Chapter 12. Simplifying Network Device Management with Netmiko


Python for Network Engineer's, Release 2.0

12.9 Connecting to Multiple Devices with Netmiko

In network management, efficiency is key, especially when dealing with multiple devices. Netmiko, with its versatility,
allows us to seamlessly connect and manage multiple devices with ease. Let’s explore how we can harness the power
of Netmiko to connect to multiple devices and execute commands across them.
To begin, let’s set up our script to connect to multiple devices and retrieve some basic information. We’ll use a list of
dictionaries to store the details of each device, such as its IP address, username, and password.

from netmiko import ConnectHandler


import getpass
import json

passwd = getpass.getpass('Please enter the password: ')

# List of device IPs


ip_list = ["172.16.10.11", "172.16.10.12"]

# Create a list of dictionaries for each device


device_list = []

# Populate the device list with device details


for ip in ip_list:
device = {
"device_type": "cisco_ios",
"host": ip,
"username": "admin",
"password": passwd, # Log in password from getpass
"secret": passwd # Enable password from getpass
}
device_list.append(device)

# Print human-readable device details using JSON formatting


json_formatted = json.dumps(device_list, indent=4)
print(json_formatted)

# Iterate over each device and connect to it


for each_device in device_list:
connection = ConnectHandler(**each_device)
connection.enable()
print(f'Connecting to {each_device["host"]}')
output = connection.send_command('show run | incl hostname')
print(output)
print(f'Closing Connection on {each_device["host"]}')
connection.disconnect()

In this script, we first prompt the user to enter the password securely using the getpass library. We then define a list
of device IPs and iterate over each one to create a list of dictionaries containing the device details.
Using Netmiko’s ConnectHandler method, we establish a connection to each device in the list. We print the hostname
of each device by executing the show run | incl hostname command and then gracefully close the connection.
With this script, we can efficiently connect to and retrieve information from multiple devices, streamlining our network
management tasks. Netmiko’s flexibility and ease of use make it a valuable tool for network automation.

12.9. Connecting to Multiple Devices with Netmiko 125


Python for Network Engineer's, Release 2.0

By leveraging Netmiko’s capabilities, we can simplify complex network operations and enhance our overall efficiency
in managing network infrastructure.

12.10 Simplifying Network Configuration with Netmiko

Managing configurations across multiple network devices can be a daunting task, but with Netmiko, it becomes a
seamless process. Let’s explore how we can leverage Netmiko to streamline configuration tasks across various devices.

12.10.1 Send Configuration Commands to Multiple Devices

With Netmiko’s send_config_set() method, configuring multiple devices becomes a breeze. Take a look at the
complete script below:

from netmiko import ConnectHandler

# Define device details for Cisco devices


devices = [
{
'device_type': 'cisco_ios',
'ip': '172.16.10.11',
'username': 'admin',
'password': 'cisco',
'secret': 'cisco123',
},
{
'device_type': 'cisco_ios',
'ip': '172.16.10.12',
'username': 'admin',
'password': 'cisco',
'secret': 'cisco123',
},
]

# Iterate through a list of device dictionaries


for device in devices:
print(f"Connecting to {device['ip']}...")
net_connect = ConnectHandler(**device)
net_connect.enable()

# Configure the device


config_commands = ['username admin pri 15 password cisco']
net_connect.send_config_set(config_commands)
net_connect.save_config()

# Display the updated configuration


output = net_connect.send_command('show running-config | section username')
print(output)

print(f'Closing Connection on {device["ip"]}')


net_connect.disconnect()

• Iterate through Devices: Loop through a list of device dictionaries, each containing device details.

126 Chapter 12. Simplifying Network Device Management with Netmiko


Python for Network Engineer's, Release 2.0

• Connect and Configure: Establish a connection to each device, enter enable mode, and configure it with a set
of commands.
• Save and Display: Save the configuration changes and display the updated configuration for verification.

12.10.2 Configuration Changes from a File

Netmiko also allows for applying configurations from a file using the send_config_from_file() method. Here’s
how you can do it:

from netmiko import ConnectHandler

# Define device details for a Cisco device


device = {
'device_type': 'cisco_ios',
'ip': '172.16.10.11',
'username': 'admin',
'password': 'cisco',
'secret': 'cisco123',
}

file = "config_file.cfg"

# Use a context manager to establish a connection to the device


with ConnectHandler(**device) as net_connect:
output = net_connect.send_config_from_file(file)
output += net_connect.save_config()

print(output)

• Establish Connection: Define device details and use a context manager to connect to the device.
• Apply Configurations: Apply configurations from the specified file to the device and save the changes.
• Print Output: Print any output or error messages for reference.

12.10.3 Exception Handling

Handling exceptions is crucial when dealing with network devices. Netmiko provides exception classes to handle
common issues such as timeouts and authentication errors. Here’s how you can handle exceptions in your script:

from netmiko import ConnectHandler


from netmiko.ssh_exception import NetmikoTimeoutException, NetmikoAuthenticationException

# Define device details for Cisco devices with potential authentication errors
devices = [
{
'device_type': 'cisco_ios',
'ip': '172.16.10.11',
'username': 'admin',
'password': 'cisco123', # Wrong Password
},
{
(continues on next page)

12.10. Simplifying Network Configuration with Netmiko 127


Python for Network Engineer's, Release 2.0

(continued from previous page)


'device_type': 'cisco_ios',
'ip': '172.16.10.12', # Wrong IP Address
'username': 'admin',
'password': 'cisco',
}
]

# Attempt to establish a connection to each device and execute a show command


for device in devices:
try:
net_connect = ConnectHandler(**device)
output = net_connect.send_command("show ip int brief")
print(output)
except NetmikoTimeoutException:
print(f"Device {device['ip']} not reachable")
except NetmikoAuthenticationException:
print(f"Authentication failed for {device['ip']}")

• Handle Exceptions: Gracefully handle timeout and authentication exceptions and print appropriate messages
for troubleshooting.

12.10.4 Backup Device Configuration

Automating device configuration backups is essential for network engineers. Netmiko simplifies this process:

from netmiko import ConnectHandler


from datetime import datetime

# Define device details for Cisco devices


devices = [
{
"host": "172.16.10.11",
"username": "admin",
"password": "cisco",
"device_type": "cisco_ios",
},
{
"host": "172.16.10.12",
"username": "admin",
"password": "cisco",
"device_type": "cisco_ios",
}
]

# Get current timestamp


time_stamp = datetime.now().strftime("%d-%b-%Y")

# Retrieve the running configuration of each device and save it to a file with a␣
˓→timestamp

for device in devices:


net_connect = ConnectHandler(**device)
(continues on next page)

128 Chapter 12. Simplifying Network Device Management with Netmiko


Python for Network Engineer's, Release 2.0

(continued from previous page)


print(f"Initiating running config backup for {device['host']}...")
sh_run = net_connect.send_command('show run')

with open(f"{device['host']}_{time_stamp}.cfg", 'w') as f:


f.write(sh_run)
print("Backup saved")

print("Finished backup process.")

• Backup Configuration: Retrieve the running configuration of each device and save it to a file with a timestamp
for archival purposes.

12.11 Conclusion

Netmiko empowers network engineers with the ability to automate common configuration tasks across multiple devices.
By following the examples outlined above, you can streamline network management operations and enhance overall
efficiency. Dive deeper into Netmiko’s capabilities and explore additional examples in the Netmiko GitHub repository.
There are lots of additional examples here on Github.

12.11. Conclusion 129


Python for Network Engineer's, Release 2.0

130 Chapter 12. Simplifying Network Device Management with Netmiko


CHAPTER

THIRTEEN

NAPALM - UNIFIED NETWORK DEVICE MANAGEMENT

NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) is a Python library
that provides a unified interface for managing network devices from multiple vendors. It simplifies network automation
tasks by abstracting the differences between vendor-specific implementations and providing a consistent set of methods
for interacting with network devices. It provides a unified interface, allowing the same code to configure multi-vendor
devices.

13.1 Overview of NAPALM and Its Purpose

NAPALM aims to streamline network automation workflows by offering a unified interface for managing network
devices. Its key objectives include:
• Abstracting vendor-specific complexities: NAPALM abstracts the differences between vendor-specific configu-
rations, allowing network engineers to write automation scripts that work across different platforms.
• Simplifying network management: By providing a consistent set of methods for device configuration and man-
agement, NAPALM simplifies network automation tasks such as configuration changes, state monitoring, and
data retrieval.
• Promoting interoperability: NAPALM encourages interoperability between different vendor devices, making it
easier to integrate heterogeneous network environments.

13.1.1 Supported Network Operating Systems

NAPALM is used to interact with various hardware networking vendors. It works as an API on top of other APIs,
adding another level of abstraction. Here are some of the supported operating systems:
• Arista EOS
• Cisco IOS
• Cisco IOS-XR
• Cisco NX-OS
• Juniper JunOS

131
Python for Network Engineer's, Release 2.0

13.2 Installation and Configuration Steps

To get started with NAPALM, you need to install the library and configure your development environment. You can
install NAPALM using pip, the Python package manager:

pip install napalm

Once installed, you can import the NAPALM module in your Python scripts and start using its features.

13.2.1 NAPALM CLI

NAPALM installation includes a CLI tool for direct usage. You can access the help menu with:

napalm --help

Here’s an example of using NAPALM CLI to ping a device:

napalm --user admin --password cisco --vendor ios 192.168.10.10 call ping --method-
˓→kwargs "destination='192.168.10.10'"

13.2.2 IOS Prerequisites

For IOS devices, Napalm uses the Netmiko library for interaction. Here are some prerequisites:
• Enable the archive functionality for auto-rollback.
• Enable SCP for file transfers.
See more details on the Napalm Docs

13.3 Working with Different Vendor Platforms using NAPALM

NAPALM supports a wide range of networking vendors, including Cisco, Juniper, Arista, and many others. It provides
a consistent interface for interacting with devices from these vendors, allowing you to write automation scripts that
work across multiple platforms.
Here’s an example of how to connect to a Cisco router using NAPALM:

import napalm

# Create a NAPALM driver for Cisco IOS


driver = napalm.get_network_driver("ios")

# Connect to the device


device = driver(hostname="172.16.10.12", username="admin", password="cisco")
device.open()

# Perform operations on the device

# Close the connection


device.close()

132 Chapter 13. NAPALM - Unified Network Device Management


Python for Network Engineer's, Release 2.0

13.3.1 Retrieving Device Information and Configuration Data

NAPALM provides methods for retrieving various types of information from network devices, including device details,
interfaces, routing tables, and configuration data. Here’s how you can retrieve the device information from a device:

import napalm
import json

# Create a NAPALM driver for Cisco IOS


driver = napalm.get_network_driver("ios")

# Connect to the device


device = driver(hostname="172.16.10.12", username="admin", password="cisco")
device.open()

# Retrieve the running configuration


device_facts = device.get_facts()

# Print the running configuration


print(json.dumps(device_facts, indent=4))

# Close the connection


device.close()

{
"uptime": 3360.0,
"vendor": "Cisco",
"os_version": "3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(15)T14, RELEASE␣
˓→SOFTWARE (fc2)",

"serial_number": "FTX0945W0MY",
"model": "3725",
"hostname": "R1",
"fqdn": "R1.tech.com",
"interface_list": [
"FastEthernet0/0",
"FastEthernet0/1",
"Loopback0",
"Loopback2",
"Loopback3",
"Loopback4"
]
}

13.3. Working with Different Vendor Platforms using NAPALM 133


Python for Network Engineer's, Release 2.0

13.3.2 Applying Configuration Changes and Managing Network State

In network management, applying configuration changes and ensuring the stability of network states are essential tasks.
NAPALM simplifies these operations by providing a unified interface for managing configurations across various net-
work devices. Let’s explore how NAPALM can be utilized to configure network interfaces and maintain network states
effectively.

Example 1: Configuring Interface Description

This example demonstrates how to configure the description for an interface on a Cisco router using NAPALM:

import napalm

# Create a NAPALM driver for Cisco IOS


driver = napalm.get_network_driver("ios")

# Connect to the device


device = driver(hostname="172.16.10.11", username="admin", password="cisco")
device.open()

# Load the configuration changes


config = "interface GigabitEthernet0/3\ndescription Test Interface"
device.load_merge_candidate(config=config)

# Apply the configuration changes


device.commit_config()

# Close the connection


device.close()

In this example, we establish a connection to the Cisco router and load a configuration change to set the description for
interface GigabitEthernet0/3. Subsequently, the changes are committed to ensure they take effect.

Example 2: Managing Network Configurations

The following example demonstrates how to manage network configurations by loading a candidate configuration from
a file, comparing it with the running configuration, and applying the changes if necessary:

import napalm

# Create a NAPALM driver for Cisco IOS


driver = napalm.get_network_driver("ios")

# Connect to the device


device = driver("172.16.10.11", "admin", "cisco")
device.open()

# Load the candidate configuration from a file


device.load_merge_candidate(filename="acl.cfg")

# Compare the candidate configuration with the running configuration


difference = device.compare_config()
(continues on next page)

134 Chapter 13. NAPALM - Unified Network Device Management


Python for Network Engineer's, Release 2.0

(continued from previous page)

# Check for differences and apply the changes if necessary


if len(difference) > 0:
print("Configuration changes detected:")
print(difference)
device.commit_config()
else:
print("No changes required.")
device.discard_config()

# Close the connection


device.close()

In this example, we load a candidate configuration from a file named acl.cfg and compare it with the running con-
figuration. If there are differences, the changes are applied, and if not, the candidate configuration is discarded.
These examples demonstrate the versatility of NAPALM in configuring network devices and managing network states
efficiently.

13.4 Integrating NAPALM with Other Automation Tools and Frame-


works

NAPALM integrates seamlessly with other automation tools and frameworks, allowing you to build comprehensive
automation workflows. You can use NAPALM alongside tools like Ansible, SaltStack, and Puppet to automate network
provisioning, configuration management, and monitoring tasks.

13.5 Configuration support Method

NAPALM offers various methods for configuration support, such as replace, merge, commit confirm, compare config,
atomic changes, and rollback. You can find more details in the official docs.

13.4. Integrating NAPALM with Other Automation Tools and Frameworks 135
Python for Network Engineer's, Release 2.0

136 Chapter 13. NAPALM - Unified Network Device Management


CHAPTER

FOURTEEN

GETTING STARTED WITH NORNIR - NETWORK AUTOMATION

Nornir is a Python library designed for network automation tasks. It allows Network Engineers to manage and automate
their network devices using Python. Unlike tools like Ansible that use domain-specific languages, Nornir leverages the
full power of Python, providing more flexibility and control over your automation scripts.
If you’re familiar with Ansible, you know that you first set up your inventory, write tasks, and then execute them on all
or selected devices concurrently. Nornir works similarly, but the key difference is that you use Python code instead of
a domain-specific language.

14.1 Prerequisites and Key Points

Before diving into Nornir, you should have a good understanding of Python basics. If you’re new to Python, check out
my Python Book to build a solid foundation.
Remember, Nornir isn’t meant to replace tools like Netmiko or Napalm; it’s designed to work alongside them. Think
of Nornir as a framework that organizes your automation tasks. For example, to SSH into network devices, you’ll still
use plugins like Netmiko. We’ll cover how these tools integrate with Nornir in the upcoming sections.
Installing Nornir is easy. Just run the following pip install command:

pip install nornir

14.2 Overview of Nornir Components

Here’s a quick overview of the main components of Nornir. Together, these elements create a powerful framework for
network automation.
• Inventory: This is where you store information about your devices. Nornir’s inventory system is flexible, allow-
ing you to define devices, their credentials, and other details in a structured format.
• Tasks: These are the actions you want to perform on your devices, like sending commands or configurations. In
Nornir, you write tasks as Python functions.
• Plugins: Nornir supports plugins to extend its functionality. Plugins can be used for tasks, inventory manage-
ment, or adding new features.
• Parallel Execution: One of Nornir’s strengths is its ability to run tasks in parallel across multiple devices. This
feature speeds up network automation tasks significantly, especially for large networks.
• Results: Nornir has a powerful feature called Results. After executing tasks on your devices, Nornir collects and
stores the outcomes in a Results object.
We will go through each of these components in detail with some examples.

137
Python for Network Engineer's, Release 2.0

14.2.1 Project Directory Structure

Here is my directory structure and the files (ignore nornir.log, which is created automatically):

(.venv) zolo@u22s:~/nornir-lab$ tree


.
config.yaml
defaults.yaml
groups.yaml
hosts.yaml
nornir.log
nornir_test.py

0 directories, 6 files

14.3 Configuring Nornir

14.3.1 Configuration File (config.yaml)

The config.yaml file is a configuration for Nornir that outlines how it should manage its inventory and execute tasks.
It’s written in YAML, a human-readable data format, making it easy to understand and modify.

# config.yaml
---
inventory:
plugin: SimpleInventory
options:
host_file: 'hosts.yaml'
group_file: 'groups.yaml'
defaults_file: 'defaults.yaml'

runner:
plugin: threaded
options:
num_workers: 5

• Inventory: Specifies how Nornir should load information about network devices. It uses the SimpleInventory
plugin and points to three files (other inventory plugins can read from Ansible’s inventory files or tools like
NetBox):
– hosts.yaml for individual device details
– groups.yaml for settings common to groups of devices
– defaults.yaml for default settings applicable to all devices unless overridden in the other files.
• Runner: Controls how Nornir runs tasks across devices. Here, the threaded plugin is used with num_workers
set to 5, meaning tasks will be executed in parallel on up to 5 devices at a time.

138 Chapter 14. Getting Started with Nornir - Network Automation


Python for Network Engineer's, Release 2.0

14.3.2 Host File (hosts.yaml)

This file contains details about each network device. For every device, you can specify parameters such as its hostname,
IP address, platform type (e.g., Cisco, Arista), and credentials. Nornir uses this information to connect to and manage
the devices individually.

# hosts.yaml
---
sw1:
hostname: 172.16.10.11
groups:
- cisco_switch

R1:
hostname: 172.16.10.12
groups:
- cisco_router

14.3.3 Groups File (groups.yaml)

The groups.yaml file is used to define common settings for groups of devices. For example, if you have several devices
from the same vendor or within the same part of your network, you can group them and assign shared parameters like
vendor or credentials. Devices in hosts.yaml can be associated with one or more groups, inheriting the group’s
settings.

# groups.yaml
---
cisco_switch:
platform: cisco_ios

cisco_router:
platform: cisco_ios

14.3.4 Defaults File (defaults.yaml)

The defaults.yaml file provides default settings that apply to all devices unless explicitly overridden in hosts.yaml
or groups.yaml. This is useful for global settings like default credentials, timeout values, or any other parameters you
want to apply network-wide. Here, I’ve defined the default credentials.

# defaults.yaml
---
username: admin
password: cisco

14.3. Configuring Nornir 139


Python for Network Engineer's, Release 2.0

14.4 Writing and Running Your First Nornir Script

14.4.1 Basic Nornir Script

Let’s look at a simple example to understand how our first Nornir script works, using the inventory examples we
discussed before (with Cisco devices).

# nornir hello script


from nornir import InitNornir

def say_hello(task):
print("Hello, Nornir")

nr = InitNornir(config_file="config.yaml")
nr.run(task=say_hello)

(.venv) zolo@u22s:~/nornir-lab$ python nornir_test.py


Hello, Nornir
Hello, Nornir

• Importing Nornir: The script starts by importing the InitNornir class from the Nornir library. This is essential
for initializing our Nornir environment.
• Defining a Task Function: Next, we define a simple task function, say_hello, that takes task as an argument.
This function simply prints a message, “Hello, Nornir”. In Nornir, tasks are functions that you want to execute
on your network devices. The task argument represents the task being executed and carries information about
the current device it’s running on.
• Initializing Nornir: We then create an instance of Nornir using InitNornir, specifying config.yaml as
the configuration file. This configuration includes our inventory setup with hosts.yaml, groups.yaml, and
defaults.yaml, defining our network devices and their properties.
• Running the Task: Finally, we use the .run() method on our Nornir instance to execute the say_hello task
across all devices specified in our inventory. Because our config.yaml specifies a runner with 5 workers, tasks
can be executed in parallel on up to 5 devices at a time.
• Output: Given our inventory setup, the script prints “Hello, Nornir” once for each device in the inventory. Since
we have two devices (sw1 and R1), we see the message printed twice, indicating the task executed successfully
on each device.

14.4.2 Using the print_result Plugin

Let’s look at our second example to see how to use the print_result plugin. If you have used Ansible before, you
know it provides a nice output showing what’s going on.
You can install the plugin using the pip install command:

pip install nornir_utils

# nornir print script


from nornir import InitNornir
from nornir_utils.plugins.functions import print_result

def say_hello(task):
(continues on next page)

140 Chapter 14. Getting Started with Nornir - Network Automation


Python for Network Engineer's, Release 2.0

(continued from previous page)


return "Hello, Nornir"

nr = InitNornir(config_file="config.yaml")
result = nr.run(task=say_hello)
print_result(result)

In this updated example, the significant addition is the use of print_result from the nornir_utils plugin. This
function is designed to neatly display the results of tasks executed by Nornir on your network devices.
• Importing print_result: We’ve added a new import statement to bring in the print_result function. This
plugin is used for formatting and printing the outcome of our tasks in a readable manner.
• Storing and Printing Results: Instead of directly printing a message within the say_hello task, we now return
the message. The main script captures the output of the nr.run method in a variable named result. This
variable holds detailed information about the task execution on each device. Finally, print_result(result)
is called to display this information.

(.venv) zolo@u22s:~/nornir-lab$ python nornir_test2.py


say_hello***********************************************************************
* R1 ** changed : False ********************************************************
vvvv say_hello ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
Hello, Nornir
^^^^ END say_hello ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* sw1 ** changed : False ********************************************************
vvvv say_hello ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
Hello, Nornir
^^^^ END say_hello ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

• Output: The output from print_result shows a structured view of the task execution. For each device (R1 and
sw1), it indicates the task name (say_hello), the status (changed: False), and the message returned by the task
(Hello, Nornir). This format makes it easy to understand what happened during the script’s execution and to
troubleshoot if necessary.

14.4.3 Accessing the Host’s Parameters

The task.host object allows us to access various parameters of the host on which the task is currently executing. You
can retrieve specific details like:
• task.host: The name of the current device.
• task.host.groups: The group(s) the device belongs to.
• task.host.hostname: The hostname or IP address of the device.
By using task.host along with its attributes, we can dynamically insert each device’s specific information into our
task’s return message.

# nornir access host script


from nornir import InitNornir
from nornir_utils.plugins.functions import print_result

def say_hello(task):
return f"Hello, {task.host} - {task.host.groups} - {task.host.hostname}"

(continues on next page)

14.4. Writing and Running Your First Nornir Script 141


Python for Network Engineer's, Release 2.0

(continued from previous page)


nr = InitNornir(config_file="config.yaml")
result = nr.run(task=say_hello)
print_result(result)

(.venv) zolo@u22s:~/nornir-lab$ python nornir_test3.py


say_hello***********************************************************************
* R1 ** changed : False ********************************************************
vvvv say_hello ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
Hello, R1 - [Group: cisco_device] - 172.16.10.12
^^^^ END say_hello ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* sw1 ** changed : False ********************************************************
vvvv say_hello ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
Hello, sw1 - [Group: cisco_device] - 172.16.10.11
^^^^ END say_hello ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This script shows how to use task.host to access and display details about each device, including its name, groups,
and hostname.

14.4.4 Filtering Devices with Nornir

Here’s another example of how you can run tasks on specific devices.

# nornir filter script


from nornir import InitNornir
from nornir_utils.plugins.functions import print_result

def say_hello(task):
return f"Hello, {task.host} - {task.host.groups} - {task.host.hostname}"

nr = InitNornir(config_file="config.yaml")
nr = nr.filter(hostname="172.16.10.11")

result = nr.run(task=say_hello)
print_result(result)

In this script, we’re using the filter method to narrow down the devices based on their hostname. Specifically, we’re
filtering for devices with the hostname “172.16.10.11”, which corresponds to switchs in our inventory. Then, we run
the say_hello task only on these filtered devices. Finally, we print the results using the print_result function.

14.5 Integrating Netmiko with Nornir

Now, we’ve reached the really exciting part where we can actually execute commands on devices and see the output.
You might think, like I did when I was just getting started, “Alright, I’ll just create a new function, import Netmiko’s
ConnectHandler, and get on with it, right?”
But here’s a pleasant surprise: the awesome teams behind Nornir and Netmiko have already done a lot of the heavy
lifting for us. They’ve created plug-ins that we can easily import. To get the netmiko plug-in, all you need to do is
run pip install nornir_netmiko. This simple command fetches and installs everything you need to start sending
commands to your network devices through your Nornir scripts.

142 Chapter 14. Getting Started with Nornir - Network Automation


Python for Network Engineer's, Release 2.0

14.5.1 Installing the nornir_netmiko Plugin

pip install nornir_netmiko

14.5.2 Sending Commands with nornir_netmiko

# nornir show cmd script


from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")

results = nr.run(
task=netmiko_send_command, command_string="show ip interface brief | excl down"
)
print_result(results)

In this script, we’re leveraging the nornir_netmiko plugin, particularly the netmiko_send_command function, to ex-
ecute commands on network devices. After initializing Nornir, we call nr.run, passing in netmiko_send_command
as the task. We specify the command we want to run on our devices with command_string='show ip interface
brief | excl down'.

14.5.3 Output of Sending Commands

(.venv) zolo@u22s:~/nornir-lab$ python nornir_netmiko_show.py


netmiko_send_command************************************************************
* R1 ** changed : False ********************************************************
vvvv netmiko_send_command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 172.16.10.12 YES NVRAM up up
^^^^ END netmiko_send_command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* sw1 ** changed : False *******************************************************
vvvv netmiko_send_command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 unassigned YES unset up up
GigabitEthernet3/3 unassigned YES unset up up
Vlan1 172.16.10.11 YES NVRAM up up
^^^^ END netmiko_send_command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

14.5. Integrating Netmiko with Nornir 143


Python for Network Engineer's, Release 2.0

14.5.4 Configuring Devices with Nornir and Netmiko

The netmiko_send_config function is utilized to push configuration commands to devices, specifically targeting
router devices with hostname="172.16.10.12".
After filtering for these devices, we execute netmiko_send_config to send configuration commands. The output
marked changed : True indicates that the configuration was successfully applied, reflecting changes made on the
devices.

# nornir config cmd script


from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_config
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")
nr = nr.filter(hostname="172.16.10.12")
results = nr.run(task=netmiko_send_config, config_commands=["ntp server 1.1.1.1"])
print_result(results)

(.venv) zolo@u22s:~/nornir-lab$ python nornir_netmiko_conf.py


netmiko_send_config*************************************************************
* R1 ** changed : True *********************************************************
vvvv netmiko_send_config ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
R1(config)#ntp server 1.1.1.1
R1(config)#end
R1#
^^^^ END netmiko_send_config ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

14.5.5 Dynamic Configuration with Host-Specific Data

I have made a slight change to the script to demonstrate a more dynamic feature of Nornir: accessing host-specific data
within a task function for customized configurations across devices.
In the updated hosts.yaml file, you’ll notice an additional data section under R1. This section allows us to define cus-
tom data applicable to devices. Here, we’ve specified an NTP server address (1.1.1.1) under data, making it accessible
to devices associated with this router.

from nornir import InitNornir


from nornir_netmiko.tasks import netmiko_send_config
from nornir_utils.plugins.functions import print_result

def set_ntp(task):
ntp_server = task.host["ntp"]
task.run(task=netmiko_send_config, config_commands=[f"ntp server {ntp_server}"])

nr = InitNornir(config_file="config.yaml")
nr = nr.filter(hostname="172.16.10.12")

results = nr.run(task=set_ntp)
print_result(results)

144 Chapter 14. Getting Started with Nornir - Network Automation


Python for Network Engineer's, Release 2.0

The function set_ntp fetches the NTP server address using task.host['ntp'], dynamically inserting it into the
configuration command. This method ensures that the NTP server setting applied to each device is retrieved from the
inventory, allowing for centralized management of device configurations.

(.venv) zolo@u22s:~/nornir-lab$ python nornir_netmiko_data.py


set_ntp*************************************************************************
* R1 ** changed : True *********************************************************
vvvv set_ntp ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- netmiko_send_config ** changed : True ------------------------------------- INFO
configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
R1(config)#ntp server 1.1.1.1
R1(config)#end
R1#
^^^^ END set_ntp ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

14.5.6 Explanation of task.run and nr.run

In this example, you would have seen two different ways to run tasks: task.run and nr.run. Here’s a brief explanation
of the difference between the two:
• task.run: This is used within a task function to execute another task. Think of it as calling a sub-task within
your main task. When you use task.run, you’re essentially saying, “While performing this task, go ahead and
run these additional tasks as part of it.”
• nr.run: On the other hand, nr.run is used to kick off tasks at the top level. This is the method you call when
you want to start your automation process and execute tasks across your inventory of devices.
In summary, nr.run is used to initiate your automation tasks on your network devices, while task.run allows you to
organize and modularize your tasks by calling other tasks within a task.

14.6 Integrating Python NAPALM with Nornir

In this section, we will extend our network automation capabilities by integrating NAPALM (Network Automation
and Programmability Abstraction Layer with Multivendor support) with Nornir. NAPALM provides a common API to
interact with different network devices, supporting several network operating systems like IOS, Junos, and EOS.

14.6.1 Installing NAPALM

First, we need to install NAPALM. You can install it using pip:

pip install napalm

Additionally, we need to install the Nornir NAPALM plugin:

pip install nornir_napalm

14.6. Integrating Python NAPALM with Nornir 145


Python for Network Engineer's, Release 2.0

14.6.2 Using NAPALM with Nornir

Let’s begin with a basic example of using NAPALM to retrieve data from our network devices. We’ll use NAPALM to
get the interfaces’ IP addresses.

# nornir napalm get interfaces ip script


from nornir import InitNornir
from nornir_napalm.tasks import napalm_get
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")

results = nr.run(
task=napalm_get, getters=["interfaces_ip"]
)
print_result(results)

In this script, we initialize Nornir and then run the napalm_get task with the getter interfaces_ip to retrieve the IP
addresses of the interfaces.

(.venv) zolo@u22s:~/nornir-lab$ python nornir_napalm_int.py


napalm_get**********************************************************************
* R1 ** changed : False ********************************************************
vvvv napalm_get ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
{ 'interfaces_ip': { 'FastEthernet0/0': { 'ipv4': { '172.16.10.12': { 'prefix_length':␣
˓→24}}}}}

^^^^ END napalm_get ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


* sw1 ** changed : False *******************************************************
vvvv napalm_get ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
{'interfaces_ip': {'Vlan1': {'ipv4': {'172.16.10.11': {'prefix_length': 24}}}}}
^^^^ END napalm_get ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

14.6.3 Configuring Devices with NAPALM

We can also use NAPALM to configure devices. The following script demonstrates how to use the napalm_configure
task to push configuration changes to devices.

# nornir napalm configure script


from nornir import InitNornir
from nornir_napalm.tasks import napalm_configure
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")

def configure_ntp(task):
ntp_config = """
ntp server 1.1.1.1
"""
task.run(task=napalm_configure, configuration=ntp_config)

results = nr.run(task=configure_ntp)
print_result(results)

146 Chapter 14. Getting Started with Nornir - Network Automation


Python for Network Engineer's, Release 2.0

In this script, we define a configure_ntp function that uses napalm_configure to apply an NTP configuration to
the devices. We then run this task across our inventory.

(.venv) zolo@u22s:~/nornir-lab$ python nornir_napalm_configure.py


configure_ntp*******************************************************************
* R1 ** changed : True *********************************************************
vvvv configure_ntp ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- napalm_configure ** changed : True ---------------------------------------- INFO
+ ntp server 1.1.1.1
^^^^ END configure_ntp ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* sw1 ** changed : True ********************************************************
vvvv configure_ntp ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- napalm_configure ** changed : True ---------------------------------------- INFO
+ ntp server 1.1.1.1
^^^^ END configure_ntp ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

14.6.4 Retrieving Device Facts with NAPALM

Let’s look at another example where we retrieve basic device facts using NAPALM.

# nornir napalm get facts script


from nornir import InitNornir
from nornir_napalm.plugins.tasks import napalm_get
from nornir_utils.plugins.functions import print_result

def get_facts(task):
task.run(task=napalm_get, getters=["facts"])

nr = InitNornir(config_file="config.yaml")
result = nr.run(task=get_facts)
print_result(result)

In this script, the napalm_get task is used with the facts getter to retrieve basic information about the devices, such as
vendor, model, serial number, and uptime.

(.venv) zolo@u22s:~/nornir-lab$ python nornir_napalm_get.py


get_facts***********************************************************************
* R1 ** changed : False ********************************************************
vvvv get_facts ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- napalm_get ** changed : False --------------------------------------------- INFO
{ 'facts': { 'fqdn': 'R1.test.lab',
'hostname': 'R1',
'interface_list': ['FastEthernet0/0', 'FastEthernet0/1'],
'model': '3725',
'os_version': '3700 Software (C3725-ADVENTERPRISEK9-M), Version '
'12.4(15)T14, RELEASE SOFTWARE (fc2)',
'serial_number': 'FTX0945W0MY',
'uptime': 2040.0,
'vendor': 'Cisco'}}
^^^^ END get_facts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(continues on next page)

14.6. Integrating Python NAPALM with Nornir 147


Python for Network Engineer's, Release 2.0

(continued from previous page)


* sw1 ** changed : False *******************************************************
vvvv get_facts ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- napalm_get ** changed : False --------------------------------------------- INFO
{ 'facts': { 'fqdn': 'SW1.test.lab',
'hostname': 'SW1',
'interface_list': [ 'GigabitEthernet0/0',
'GigabitEthernet0/1',
'GigabitEthernet0/2',
'GigabitEthernet0/3',
'GigabitEthernet1/0',
'GigabitEthernet1/1',
'GigabitEthernet1/2',
'GigabitEthernet1/3',
'GigabitEthernet2/0',
'GigabitEthernet2/1',
'GigabitEthernet2/2',
'GigabitEthernet2/3',
'GigabitEthernet3/0',
'GigabitEthernet3/1',
'GigabitEthernet3/2',
'GigabitEthernet3/3',
'Vlan1'],
'model': 'IOSv',
'os_version': 'vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), '
'Experimental Version 15.2(20200924:215240) '
'[sweickge-sep24-2020-l2iol-release 135]',
'serial_number': '9BPOBFRTOEY',
'uptime': 3960.0,
'vendor': 'Cisco'}}
^^^^ END get_facts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

14.6.5 Summary

Nornir, integrated with NAPALM and supported by plugins like nornir_netmiko and nornir_utils, offers a robust solu-
tion for network automation tasks. This combination enhances automation capabilities, allowing for efficient retrieval
and configuration of network device data. With NAPALM’s multivendor support and common API, along with Nornir’s
powerful task management, network automation becomes more manageable, scalable, and tailored to your specific
needs. This guide has demonstrated how to set up Nornir, create basic and advanced scripts, utilize host-specific data
for dynamic configurations, and leverage plugins to efficiently manage and configure network devices.

148 Chapter 14. Getting Started with Nornir - Network Automation


CHAPTER

FIFTEEN

APPENDIX

• Python dotenv
• Essential VS Code Settings
• Linux Networking Commands

15.1 Simplifying Network Automation with Python dotenv

In today’s network engineering world, automation is incredibly important. It helps make tasks smoother, faster, and
more efficient. Python has become a top choice for automating network tasks due to its versatility and extensive range of
libraries. However, handling sensitive information like passwords and special keys securely within automation scripts
can be challenging for newcomers. In this blog, we’ll delve into how Python dotenv simplifies this process, making
network automation safer and more manageable.

15.2 Understanding Python-dotenv

Python-dotenv is a popular library that simplifies the management of environment variables in Python applications. It
allows you to store configuration settings in a .env file and easily load them into your script. This means you can keep
sensitive information out of your source code and version control, reducing the risk of exposure.

15.3 Key Benefits of Python-dotenv

• Follows 12-factor principles: Python-dotenv adheres to the 12-factor principles for building scalable and main-
tainable applications, ensuring consistency and reliability.
• Simplifies development and testing: By enabling the use of different .env files for specific environments (e.g.,
development, production), Python-dotenv streamlines the development and testing process.
• Supports various formats: It supports variable expansion, multiline values, and comments in the .env file format,
providing flexibility and ease of use.
• Cross-platform compatibility: Python-dotenv is compatible with any system, allowing for seamless integration
across different environments.
• Integration with other Python libraries: It integrates well with other Python libraries and frameworks, such as
Flask, Django, and IPython, enhancing its versatility and utility.

149
Python for Network Engineer's, Release 2.0

15.4 Getting Started with Python-dotenv

To begin using Python-dotenv in your projects, you’ll first need to install it via pip:

pip install python-dotenv

Once installed, you can create a .env file in the root directory of your project to store your environment variables.
Here’s an example of what a .env file might look like:

SSH_USERNAME=admin
SSH_PASSWORD=cisco123

Remember, each line in the .env file represents a single environment variable, with the key and value separated by an
equals sign =. It’s essential to keep your .env file secure and out of version control by adding it to your .gitignore
file.

15.5 Using Python-dotenv in Your Scripts

With the .env file in place, you can load the environment variables into your Python script using Python-dotenv. Here’s
how you can do it:

import os
from dotenv import load_dotenv

# Load environment variables from .env file


load_dotenv()

# Access environment variables


ssh_username = os.getenv("SSH_USERNAME")
ssh_password = os.getenv("SSH_PASSWORD")

By using os.getenv(), you can retrieve the values of your environment variables within your script securely.
You can use comments in your .env file by prefixing a line with the pound (#) character. Comments are ignored by
python-dotenv when it loads the environment variables from the file. Here’s an example of how to use comments in
your .env file:

# Credentials
SSH_USERNAME=admin
SSH_PASSWORD=cisco123

# API configuration
API_SECRET=0987654321fedcba

Alternatively, we can use os.environ.get() from os module to access environment variables directly without
python-dotenv. This approach is useful when we want to avoid an extra dependency.

150 Chapter 15. Appendix


Python for Network Engineer's, Release 2.0

15.5.1 Python Automation Script

Now, let’s create a Python script to automate the backup process using the environment variables from the .env file:

# Import necessary modules


import os
import paramiko
from dotenv import load_dotenv

# Load environment variables from .env file


load_dotenv()

# Function to connect to device and retrieve configuration


def backup_config():
# Get environment variables
device_ip = os.getenv("DEVICE_IP")
ssh_username = os.getenv("SSH_USERNAME")
ssh_password = os.getenv("SSH_PASSWORD")

# Connect to the device


ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=device_ip, username=ssh_username, password=ssh_password)

# Execute command to retrieve configuration


stdin, stdout, stderr = ssh_client.exec_command("show running-config")
config = stdout.read().decode()

# Save configuration to a file


with open(f"{device_ip}_config.txt", "w") as file:
file.write(config)

# Close SSH connection


ssh_client.close()
print(f"Configuration backup for {device_ip} completed.")

# Execute the backup_config function


if __name__ == "__main__":
backup_config()

When you run this script, it will load the environment variables from the .env file and use them to connect to the device
via SSH. It will then retrieve the running configuration and save it to a file named after the device’s IP address.
By using python-dotenv, you can keep your credentials and device information secure in the .env file, separate from
your codebase. This makes it easier to manage configurations and ensures that sensitive information is not exposed in
your scripts.

15.5. Using Python-dotenv in Your Scripts 151


Python for Network Engineer's, Release 2.0

15.6 Python Development: Essential VS Code Settings

Visual Studio Code (VS Code) is a versatile and powerful code editor developed by Microsoft. It provides a rich
set of features for various programming languages, including excellent support for Python development. This blog
will explore some essential VS Code settings for Python programming, focusing on creating virtual environments and
configuring the Code Runner extension.

15.7 What is VS Code?

Visual Studio Code is a free, open-source code editor that is highly customizable and supports many programming
languages. It comes with robust features, extensions, and integrations that enhance the development experience.

15.8 What is a Virtual Environment?

A virtual environment is an isolated Python environment that allows you to manage dependencies and packages for a
specific project. It helps prevent conflicts between different projects by creating a dedicated space for each.

15.8.1 Setting Up VS Code

Open the Command Palette

Press Ctrl + Shift + P to open the Command Palette in VS Code.

Create a Virtual Environment

Enter Python: Create Environment into the search bar of the Command Palette. You can choose options such as .venv
or conda based on your preference. This step ensures that your Python project has its dedicated virtual environment.

Install Code Runner Extension

Go to the Extensions view by Ctrl + Shift + X. Search for Code Runner and install the extension. Code Runner allows
you to run your Python code directly from the editor into your terminal.

Configure Code Runner

Navigate to File => Preferences => Settings and search for Run Code Configuration.” Locate the checkbox for Run in
Terminal and ensure it is checked. This setting ensures that the Code Runner extension executes your Python code in
the terminal.
Configuring Visual Studio Code for Python development can significantly improve your workflow. By creating a virtual
environment and leveraging the Code Runner extension, you ensure a clean and efficient development environment.
Now you’re ready to dive into Python programming with the enhanced tools provided by VS Code.

152 Chapter 15. Appendix


Python for Network Engineer's, Release 2.0

15.9 Essential Linux Networking Commands

As a network engineer, mastering various Linux networking commands is crucial for effective network management
and troubleshooting. This appendix covers essential commands, providing examples and explanations to help you
understand their usage and importance.

15.9.1 1. ifconfig

The ifconfig command is used to configure network interfaces. It displays information about all active interfaces
and allows you to assign IP addresses, enable or disable interfaces, and more.
• Example:

ifconfig eth0

Displays information about the eth0 interface.

15.9.2 2. ip

The ip command is a powerful tool for network configuration. It replaces older tools like ifconfig and route. It
can be used to configure IP addresses, routes, and interfaces.
• Example:

ip addr show

Shows the IP addresses assigned to network interfaces.

15.9.3 3. route

The route command is used to display and manipulate the IP routing table. It helps in adding or removing routes and
viewing the current routing table.
• Example:

route -n

Displays the routing table in numeric format.

15.9.4 4. ping

The ping command checks the connectivity between your system and another host. It sends ICMP echo requests and
waits for replies, helping diagnose network issues.
• Example:

ping google.com

Sends ICMP echo requests to google.com.

15.9. Essential Linux Networking Commands 153


Python for Network Engineer's, Release 2.0

15.9.5 5. traceroute

The traceroute command traces the path packets take to reach a destination. It helps identify where delays or failures
occur in the network.
• Example:

traceroute google.com

Traces the route to google.com.

15.9.6 6. netstat

The netstat command displays network connections, routing tables, interface statistics, masquerade connections, and
multicast memberships.
• Example:

netstat -a

Lists all active connections and listening ports.

15.9.7 7. dig

The dig command queries DNS servers for information about host addresses, mail exchanges, name servers, and related
information.
• Example:

dig google.com

Performs a DNS lookup for google.com.

15.9.8 8. nslookup

The nslookup command queries DNS to obtain domain name or IP address mappings and other DNS records.
• Example:

nslookup google.com

Performs a DNS lookup for google.com.

15.9.9 9. tcpdump

The tcpdump command captures and displays network packets. It is used for network troubleshooting and security
analysis.
• Example:

sudo tcpdump -i eth0

Captures packets on the eth0 interface.

154 Chapter 15. Appendix


Python for Network Engineer's, Release 2.0

15.9.10 10. ssh and sshd

The ssh command provides secure remote login and command execution. The sshd command starts the SSH daemon,
which listens for incoming SSH connections.
• Example:

ssh user@remotehost

Connects to the remote host remotehost as the user user.

15.9.11 11. telnet

The telnet command establishes text-based connections to remote systems. It is used for remote login and commu-
nication but is less secure than SSH.
• Example:

telnet example.com 80

Connects to the remote host example.com on port 80.

15.9.12 12. scp

The scp command securely transfers files between hosts over a network using SSH.
• Example:

scp file.txt user@remotehost:/remote/directory/

Copies file.txt from the local machine to the remote host.

15.9.13 13. nmap

The nmap command scans networks for hosts, open ports, and services, and detects vulnerabilities.
• Example:

nmap 192.168.1.1

Scans the host with the IP address 192.168.1.1.

15.9.14 14. arp

The arp command manipulates the ARP cache, mapping IP addresses to MAC addresses.
• Example:

arp -a

Displays all the entries in the ARP cache.

15.9. Essential Linux Networking Commands 155


Python for Network Engineer's, Release 2.0

15.9.15 15. resolvconf

The resolvconf command manages DNS server information in the /etc/resolv.conf file.
• Example:

sudo resolvconf -u

Updates the nameserver information.

15.9.16 16. nmcli

The nmcli command controls NetworkManager, allowing you to manage network connections and devices from the
command line.
• Example:

nmcli connection show

Lists all network connections.

15.9.17 17. nmtui

The nmtui command provides a text user interface for NetworkManager, making it easier to configure network settings
interactively.
• Example:

sudo nmtui

Launches the nmtui interactive menu.

15.9.18 Using man Command

To dive deeper into the documentation for these commands directly from the terminal, you can use the man (manual)
and --help options. Here’s how you can do it for each command:
The man command displays the manual page for a specified command, providing detailed information about its usage,
options, and examples.
• Syntax:

man [command]

• Examples:

man ip

156 Chapter 15. Appendix


Python for Network Engineer's, Release 2.0

15.9.19 Using --help Option

The --help option provides a brief overview of the command’s usage and available options.
• Syntax:

[command] --help

• Examples:

ip --help

15.9.20 Additional Tips

• Search within man pages: You can search for specific keywords within a man page by pressing / followed by
the keyword, and then pressing Enter. Use n to move to the next occurrence.
• Exit man pages: Press q to exit the man page and return to the terminal.
Mastering these Linux networking commands will empower you to manage and troubleshoot networks effectively.
Whether you’re configuring interfaces, monitoring traffic, or securing your network, these tools are essential for any
network engineer.

15.10 Essential Windows 10 Networking Commands

As a network engineer, mastering various Windows 10 networking commands is crucial for effective network man-
agement and troubleshooting. This blog covers essential commands, providing examples and explanations to help you
understand their usage and importance.

15.11 1. ipconfig

The ipconfig command displays all current TCP/IP network configuration values and refreshes DHCP and DNS
settings.
• Example:

ipconfig /all

Displays detailed information about all network interfaces.

15.12 2. netsh

The netsh command is a versatile tool for network configuration. It allows you to display or modify the network
configuration of a computer.
• Example:

netsh interface ip show config

Shows the IP addresses assigned to network interfaces.

15.10. Essential Windows 10 Networking Commands 157


Python for Network Engineer's, Release 2.0

15.13 3. route

The route command displays and modifies the IP routing table. It helps in adding or removing routes and viewing the
current routing table.
• Example:

route print

Displays the routing table.

15.14 4. ping

The ping command checks the connectivity between your system and another host. It sends ICMP echo requests and
waits for replies, helping diagnose network issues.
• Example:

ping google.com

Sends ICMP echo requests to google.com.

15.15 5. tracert

The tracert command traces the path packets take to reach a destination. It helps identify where delays or failures
occur in the network.
• Example:

tracert google.com

Traces the route to google.com.

15.16 6. netstat

The netstat command displays network connections, routing tables, interface statistics, and more.
• Example:

netstat -a

Lists all active connections and listening ports.

158 Chapter 15. Appendix


Python for Network Engineer's, Release 2.0

15.17 7. nslookup

The nslookup command queries DNS servers for information about host addresses, mail exchanges, name servers,
and related information.
• Example:

nslookup google.com

Performs a DNS lookup for google.com.

15.18 8. getmac

The getmac command displays the MAC address for network adapters on the system.
• Example:

getmac

Displays the MAC addresses of all network interfaces.

15.19 9. telnet

The telnet command establishes text-based connections to remote systems. It is used for remote login and commu-
nication but is less secure than SSH.
• Example:

telnet example.com 80

Connects to the remote host example.com on port 80.

15.20 10. pscp

pscp is a command-line tool from PuTTY for secure file transfer over SSH.
• Example:
1. Download pscp.exe from PuTTY’s official website.
2. Use the following command:

pscp file.txt user@remotehost:/remote/directory/

Copies file.txt from the local machine to the remote host.

15.17. 7. nslookup 159


Python for Network Engineer's, Release 2.0

15.21 11. nmap

The nmap command is available for Windows and works similarly to the Linux version.
• Example:
1. Download and install nmap from Nmap’s official website.
2. Use the following command:

nmap 192.168.1.1

Scans the host with the IP address 192.168.1.1.

15.22 12. arp

The arp command manipulates the ARP cache, mapping IP addresses to MAC addresses.
• Example:

arp -a

Displays all the entries in the ARP cache.

15.23 13. net use

The net use command connects or disconnects a computer from a shared resource or displays information about
computer connections.
• Example:

net use <drive letter>: \\<server>\<share>

Maps a network drive.

15.24 14. gpupdate

The gpupdate command refreshes Group Policy settings.


• Example:

gpupdate /force

Forces a refresh of Group Policy settings.

160 Chapter 15. Appendix


Python for Network Engineer's, Release 2.0

15.25 15. systeminfo

The systeminfo command displays detailed configuration information about a computer and its operating system.
• Example:

systeminfo

Displays system information.

15.26 16. tasklist

The tasklist command displays a list of currently running processes on the system.
• Example:

tasklist

Lists all running processes.

15.27 17. taskkill

The taskkill command terminates tasks by process id (PID) or image name.


• Example:

taskkill /PID <process id> /F

Terminates the specified process.

15.28 Using help Command

To dive deeper into the documentation for these commands directly from the command prompt, you can use the help
option. Here’s how you can do it for each command:
• Syntax:

[command] /?

• Examples:

netsh /?

15.25. 15. systeminfo 161


Python for Network Engineer's, Release 2.0

15.29 Additional Tips

• Search within help pages: You can search for specific keywords within a help page by pressing Ctrl + F
and entering the keyword.
• Exit help pages: Simply close the command prompt window or type exit to return to the terminal.
Mastering these Windows 10 networking commands will empower you to manage and troubleshoot networks effec-
tively. Whether you’re configuring interfaces, monitoring traffic, or securing your network, these tools are essential for
any network engineer.

162 Chapter 15. Appendix

You might also like