Python Automation Book Readthedocs Io en 1.0
Python Automation Book Readthedocs Io en 1.0
Release 2.0
Syed Asif
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
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
iii
13.5 Configuration support Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
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
. Warning
TABLE OF CONTENTS: 1
Python for Network Engineer's, Release 2.0
2 TABLE OF CONTENTS:
CHAPTER
ONE
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.
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
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).
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.
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.
• 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.
• 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.
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.
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.
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.
Python helps network engineers automate repetitive tasks such as making configuration changes, creating backups, and
monitoring networks.
Python-based tools like Ansible, NAPALM, and Netmiko are widely used for managing network configurations.
Python allows network engineers to create custom tools for monitoring and troubleshooting, ensuring the continuous
health of a network.
Python plays a crucial role in implementing strong security policies, analyzing network traffic, and responding to
security incidents.
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.
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
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:
For other Linux distributions, check your system’s package manager for the appropriate commands.
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.
Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32
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:
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:
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.
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:
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.
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:
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.
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.
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.
#!/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
python my_code.py
Alternatively, you can use the Python launcher with the py command:
py my_code.py
#!/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.
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:
For multi-line comments, Python allows the use of triple-quotes (''') or (""") at the beginning and end of the comment
block.
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.
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
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:
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
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.
Depending on how you installed Python, there might be other mechanisms available for installing pip, such as using
Linux package managers.
Debian/Ubuntu
On Ubuntu, pip often comes pre-installed. If not, you can install it with the following commands:
To check installed modules via pip, use pip list, and to check the pip version, use pip --version.
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.
The following command will install the latest version of a module and its dependencies from the Python Packaging
Index:
You can also specify an exact or minimum version directly on the command line:
These steps should help you get started with pip and managing Python packages effectively.
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
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:
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.
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:
virtualenv <FOLDER_NAME>
This command won’t work with the venv module. Activating, deactivating, and freezing work the same way as with
the venv module.
To make your virtual environments reproducible, you can create a requirements.txt file while your virtual environ-
ment is active:
After working or deleting your venv folder, recreate the same environment with the requirements.txt file:
virtualenv new-venv
source new-venv/bin/activate
python -m pip install -r requirements.txt
TWO
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
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.
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
You can find out the data type of a variable using the type() function.
You can also assign the same value to multiple variables at once.
If you have a list of values, you can extract them into variables.
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.
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"
Using meaningful variable names like device_name, device_ip, device_username, and device_password makes
your code more readable and easier to understand.
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.
For multi-word variable names, use underscores to separate words, following the snake_case convention. For example,
device_name is more readable than deviceName.
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.
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:
• 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.
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.
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
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:
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:
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.
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:
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:
The .splitlines() method splits a string into separate lines based on line breaks. This is useful for multiline text
data.
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:
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.
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.
This code finds the position of “ip address” in the string config.
Commonly used string methods in network scripting include upper(), split(), and find().
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.
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.
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:
Using Tuples
The % operator can also work with tuples to insert multiple values into a string:
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.
The .format() method is more modern and versatile. It replaces placeholders with values inside {}. For example:
With .format(), you can insert values in any order, repeat them, or format them in various ways.
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 {}:
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.
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:
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
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
You can format dates using f-strings to display them in a more readable format.
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.
Strings in Python have several important characteristics that make them versatile and fundamental. Let’s explore some
of these:
You can check if a specific substring exists within a string using the in operator:
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:
String Concatenation
Strings as Sequences
Strings are sequences of characters, meaning they have a defined order, and you can access their elements by index.
Strings are indexed from left to right, starting at 0 for the first character:
You can find the length of a string using the len() function and loop over the characters of a string with a for loop:
Understanding these characteristics and behaviors of strings is fundamental for working with text data in Python.
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.
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:
Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32
You can check the type of a variable using the type() function:
>>> my_var = 22
>>> print(type(my_var))
<class 'int'>
>>> result = 17 + 22
>>> print(result)
39
>>> result = 22 - 7
>>> print(result)
Output: 15
>>> result = 3 * 4
>>> print(result)
Output: 12
>>> 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.
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:
To create a float variable, just assign a number with a decimal point to it:
You can check the type of a variable using the type() function:
>>> result = 7 / 2
>>> print(result)
3.5
You can round float numbers using the round() function. For example, to round the result of 4 divided by 3 to the
nearest integer:
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.
In addition to basic arithmetic operations, Python provides other operators for working with numbers. Here are two
commonly used number operators:
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.
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.
When working with counters in Python, you can increment or decrement their values in various ways. Here are some
common methods:
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
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
>>> 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
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.
Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]␣
˓→on win32
You can convert a string literal to an integer and a float using int() and float().
Here, the string '132' is converted to the integer 132 and the float 132.0.
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.
In this case, the string '132.564' is first converted to the float 132.564 and then to the integer 132.
Python comes with several built-in functions that you can use right away. Here are some of the most useful ones:
>>> print(len('hello'))
5
This function counts the number of characters in the string 'hello', which is 5.
The round() function returns a floating-point number rounded to a specified number of decimal places.
The id() function returns a unique identifier for an object, which is its memory address.
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.
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
Arithmetic operators are used with numeric values to perform common mathematical operations.
THREE
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.
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
Creating a List
To create a list, you use square brackets [] and separate items with commas. For example:
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.
first_element = my_list[0]
print(first_element)
# Output: foo
second_element = my_list[1]
print(second_element)
# Output: 1
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]
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.
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.
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:
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.
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:
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:
if check_fruit in fruits:
print(check_fruit, "is in the list.")
else:
print(check_fruit, "is not in the list.")
The code checks if banana is in the fruits list and correctly identifies it as a member.
Python lists come with many built-in methods that let you manipulate and work with list elements easily.
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]
The clear() method removes all elements from a list, making it empty:
my_list = [1, 2, 3]
my_list.clear()
print(my_list)
# Output: []
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
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 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]
list1 = [1, 2, 3]
list2 = [4, 5, 6]
result = list1 + list2
print(result)
# Output: [1, 2, 3, 4, 5, 6]
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
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]
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]
my_list = [1, 2, 3, 4, 5]
my_list.reverse()
print(my_list)
# Output: [5, 4, 3, 2, 1]
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:
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']
list_copy = my_list[:]
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.
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.
To create a multidimensional list, you include lists as elements within another list. For example:
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:
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
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.
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.
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:
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.
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:
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:
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
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.
Tuples are often used for pairs or small sets of related data. For example, you might store IP addresses in a tuple:
You can also create a tuple without parentheses, just by separating items with commas:
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.
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.
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.
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:
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:
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.
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.
Sets store collections of unique elements and are mutable, meaning you can change them after creation.
Use the .add() method to insert a new element into a set. If the element is already in the set, nothing happens. For
example:
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:
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:
Use the .discard() method to remove an element without causing an error if the element is not in the set. For
example:
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:
These methods make it easy and efficient to modify sets in Python, making them a useful data structure for many
programming tasks.
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.
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.
In this example, result will contain all unique elements from both sf_addr and la_addr.
The intersection operation retrieves the elements that are common to both sets. To perform an intersection operation
in Python, you can use the & operator.
In this case, result will contain only the element “10.1.1.1” since it’s the common element in both sets.
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.
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_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.
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.
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:
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:
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:
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.
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:
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.
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.
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.
Python also provides the dict() constructor for creating dictionaries. This method is flexible and can handle various
input formats. Here’s an example:
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.
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:
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:
Dictionaries are not just for retrieving values; you can also update them. To change the value of an existing key, simply
reassign it:
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:
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.
Python dictionaries come with many useful methods for working with their data. These methods help you extract,
manipulate, and manage dictionary contents.
# 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.
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.
# Example Usage
del my_dict["rtr2"]
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)
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)
Nested dictionaries in Python are great for representing complex relationships and hierarchies. Let’s explore how to
use them.
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',
}
}
Here, each router (‘rtr1’ and ‘rtr2’) has its own dictionary with details like ‘host’ and ‘device_type’.
sf = {
'routers': ['192.168.1.1', '192.168.1.2'],
'switches': ['192.168.1.20', '192.168.1.21']
}
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:
In Python, objects can be either mutable or immutable. This means some objects can change their values after creation,
while others cannot.
Mutable objects can be changed after they are created. Examples include lists, dictionaries, and sets.
Immutable objects cannot be changed once they are created. Examples include strings, integers, and tuples.
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
You can use the id() function to get the unique identifier of an object in memory.
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
ssh_timeout += 1
id(ssh_timeout) # Example output: 1513546842672
• 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:
In Python, mutable objects can change their values after they are created. Let’s look at lists as an example.
Mutable objects allow their values to be modified after creation. Examples include lists, dictionaries, and sets. We’ll
focus on lists here.
The id() function gives the unique identifier for the data_center list, showing its memory location.
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.
Python allocates a continuous block of memory for a list, but it stores pointers to the elements, not the elements
themselves.
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:
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.
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.
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:
List comprehensions help generate lists and streamline tasks, making your code clearer and more concise. Here are
some practical examples:
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']
List comprehensions are great for optimizing code and enhancing network automation. They create new lists based on
existing data without modifying the original list.
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.
network_devices = [
{"name": "Router1", "status": "active"},
{"name": "Switch1", "status": "inactive"},
{"name": "Firewall1", "status": "active"},
]
configurations = [
"Router1: 192.168.1.1",
"Switch1: 10.0.0.1",
"Firewall1: 172.16.0.1",
]
Efficiency is crucial in network engineering, and list comprehensions significantly enhance code efficiency.
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.
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)
]
Mastering nested list comprehensions helps network engineers handle complex tasks and data structures efficiently.
List comprehensions are efficient and concise for data transformation tasks in network engineering. They help convert
data formats, scale values, and clean data.
List comprehensions make data transformation tasks more readable and streamlined.
Pros:
• Readability
• Efficiency
• Simplicity
Cons:
• Can be complex
• Harder to debug
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.
Set comprehensions in Python allow you to create sets dynamically from iterable objects, which is especially useful in
network automation.
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:
Set comprehensions automatically remove duplicates. For example, if you have repeated network configurations, a set
comprehension will give you only unique configurations.
Example:
You can filter items in a set comprehension using conditionals. For example, to get only routers from a set of network
devices:
Example:
Nested loops in set comprehensions allow you to combine elements from multiple iterables. This is useful for creating
sets of VLAN configurations.
Example:
You can use both nested loops and conditionals to create complex sets, such as ACL rules for network security.
Example:
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.
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.
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:
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
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.
>>> z = False
>>> result = not z
>>> print(result)
True
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
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.
In Python, values can be “truthy” or “falsy.” Understanding these helps you determine if conditions are met in your
code.
>>> 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.
Non-empty Containers: Dictionaries, sets, and other containers with elements are truthy.
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.
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.
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.
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.
Here, my_variable exists but doesn’t have any specific data. It’s like an empty container waiting to be filled.
In a Boolean context, None is considered falsy. This means that None evaluates to False in conditional statements.
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.
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.
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.
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.”
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.
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
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.
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.
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:
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.
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
• Idiomatic: Use is None to check if a variable is None. This is clearer and more explicit than using
ssh_timeout == None.
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.
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.
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
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:
73
Python for Network Engineer's, Release 2.0
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}")
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.
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}")
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.
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:
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.
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}")
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.
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)
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.
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.
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.
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.
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.
SIX
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.
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.
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()
79
Python for Network Engineer's, Release 2.0
There are several ways to read a file in Python, each suited to different needs:
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()
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()
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.
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.
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()
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()
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” 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:
SEVEN
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.
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:
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
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.
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.
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
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:
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.
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
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.
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.
EIGHT
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
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.")
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
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().
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.
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:
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)
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.
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.
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.
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.
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.”
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.
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:
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.
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)
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)
Using **kwargs, the function configure_device can handle an arbitrary number of keyword arguments, allowing
for a more dynamic configuration approach.
def tup_output(*args):
print("Tuple output:", args)
tup_output(*my_tuple)
# Output: Tuple output: (1, 2, 3)
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.
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.
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.
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.
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.
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.
Let’s take the example of creating a blueprint for a car. A car has certain attributes (properties) and actions (methods).
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:
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)
# Instance method
def type(self):
description = f"Car has {self.color} color and fuel type is {self.fuel}."
return 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.
NINE
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.
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.
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:
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
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.
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.
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.
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.
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.
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:
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.
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:
1. In the GNS3 interface, navigate to the workspace where you plan to build your network topology.
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).
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:
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.
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
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.
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:
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.
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.
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.
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.
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.
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.
TEN
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.
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.
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.
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
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
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
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.
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!
ELEVEN
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
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:
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.
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
109
Python for Network Engineer's, Release 2.0
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
# Send command
ssh_client.send("sh ip int bri\n")
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
client = paramiko.SSHClient()
# Send command
ssh_client.send("sh ip int bri\n")
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.
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
# ssh_client.send("cisco\n")
ssh_client.send("conf ter\n")
ssh_client.send("end\n")
ssh_client.send("show ip int brief\n")
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.
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())
client.connect(
hostname=host,
username="admin",
(continues on next page)
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()
**************************************************************************
* 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#
This script demonstrates the scalability of Paramiko, allowing network engineers to connect and interact with multiple
devices seamlessly.
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
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")
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()
# **************************************************************************
# * 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#
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)
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.
TWELVE
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.
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
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:
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.
To establish an SSH connection to a device using Netmiko, import the ConnectHandler class and provide the neces-
sary device details:
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
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:
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:
connection.disconnect()
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.
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:
And when we’re done, it’s good practice to gracefully close the connection:
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.
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:
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:
Once we’re done with our configuration tasks, it’s important to exit Global Configuration Mode using the
exit_config_mode() method:
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:
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.
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.
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
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!
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:
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:
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:
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.
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.
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.
By leveraging Netmiko’s capabilities, we can simplify complex network operations and enhance our overall efficiency
in managing network infrastructure.
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.
With Netmiko’s send_config_set() method, configuring multiple devices becomes a breeze. Take a look at the
complete script below:
• Iterate through Devices: Loop through a list of device dictionaries, each containing device details.
• 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.
Netmiko also allows for applying configurations from a file using the send_config_from_file() method. Here’s
how you can do it:
file = "config_file.cfg"
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.
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:
# 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)
• Handle Exceptions: Gracefully handle timeout and authentication exceptions and print appropriate messages
for troubleshooting.
Automating device configuration backups is essential for network engineers. Netmiko simplifies this process:
# Retrieve the running configuration of each device and save it to a file with a␣
˓→timestamp
• 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.
THIRTEEN
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.
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.
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
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:
Once installed, you can import the NAPALM module in your Python scripts and start using its features.
NAPALM installation includes a CLI tool for direct usage. You can access the help menu with:
napalm --help
napalm --user admin --password cisco --vendor ios 192.168.10.10 call ping --method-
˓→kwargs "destination='192.168.10.10'"
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
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
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
{
"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"
]
}
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.
This example demonstrates how to configure the description for an interface on a Cisco router using NAPALM:
import napalm
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.
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
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.
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.
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
FOURTEEN
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.
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:
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
Here is my directory structure and the files (ignore nornir.log, which is created automatically):
0 directories, 6 files
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.
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
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
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
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).
def say_hello(task):
print("Hello, Nornir")
nr = InitNornir(config_file="config.yaml")
nr.run(task=say_hello)
• 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.
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:
def say_hello(task):
(continues on next page)
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.
• 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.
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.
def say_hello(task):
return f"Hello, {task.host} - {task.host.groups} - {task.host.hostname}"
This script shows how to use task.host to access and display details about each device, including its name, groups,
and hostname.
Here’s another example of how you can run tasks on specific devices.
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.
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.
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'.
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.
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)
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.
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)
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.
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.
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.
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.
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.
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.
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)
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.
Let’s look at another example where we retrieve basic device facts using NAPALM.
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.
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.
FIFTEEN
APPENDIX
• Python dotenv
• Essential VS Code Settings
• Linux Networking Commands
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.
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.
• 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
To begin using Python-dotenv in your projects, you’ll first need to install it via pip:
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.
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
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.
Now, let’s create a Python script to automate the backup process using the environment variables from the .env file:
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.
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.
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.
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.
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.
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.
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.
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
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
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
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
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
15.9.6 6. netstat
The netstat command displays network connections, routing tables, interface statistics, masquerade connections, and
multicast memberships.
• Example:
netstat -a
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
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
15.9.9 9. tcpdump
The tcpdump command captures and displays network packets. It is used for network troubleshooting and security
analysis.
• Example:
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
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
The scp command securely transfers files between hosts over a network using SSH.
• Example:
The nmap command scans networks for hosts, open ports, and services, and detects vulnerabilities.
• Example:
nmap 192.168.1.1
The arp command manipulates the ARP cache, mapping IP addresses to MAC addresses.
• Example:
arp -a
The resolvconf command manages DNS server information in the /etc/resolv.conf file.
• Example:
sudo resolvconf -u
The nmcli command controls NetworkManager, allowing you to manage network connections and devices from the
command line.
• Example:
The nmtui command provides a text user interface for NetworkManager, making it easier to configure network settings
interactively.
• Example:
sudo nmtui
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
The --help option provides a brief overview of the command’s usage and available options.
• Syntax:
[command] --help
• Examples:
ip --help
• 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.
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
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:
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
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
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
15.16 6. netstat
The netstat command displays network connections, routing tables, interface statistics, and more.
• Example:
netstat -a
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
15.18 8. getmac
The getmac command displays the MAC address for network adapters on the system.
• Example:
getmac
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
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:
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
The arp command manipulates the ARP cache, mapping IP addresses to MAC addresses.
• Example:
arp -a
The net use command connects or disconnects a computer from a shared resource or displays information about
computer connections.
• Example:
gpupdate /force
The systeminfo command displays detailed configuration information about a computer and its operating system.
• Example:
systeminfo
The tasklist command displays a list of currently running processes on the system.
• Example:
tasklist
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 /?
• 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.