Skip to main content

Wider Page

 

Bigger text

 

In the software world, people can’t agree on a single way to solve any one problem. Different developers, companies, and communities often prefer different approaches because they try to solve different problems. That is why so many programming languages exist, and new ones continue to appear. There are a lot, really, as you can see in the screenshot below.

Programming Languages Universy
Figure 1. Programming Languages Universe.

Each language is designed with certain goals and a specific problem in mind. Some focus on performance and low-level hardware control, while others focus on simplicity and faster development. As a result, every language has its own strengths, limitations, and areas where it works best.

Of all those programming languages, Python is currently ranked as number 1 as most widely used. But why is that? What makes Python best? Let’s zoom in on the different properties of a programming language and see where Python fits in.

Typically, the three most distinctive properties of a language are whether it is low-level or high-level, whether it is compiled or interpreted, and whether it is statically or dynamically typed:

  • High-level vs. Low-level describes how far the language is from the hardware.
  • Compiled vs. Interpreted describes how the language implementation executes the code.
  • Statically vs. Dynamically typed describes when variable types are checked and whether they can change during program execution.

Low-level vs. High-level Programming Languages

Programming languages can be described as low-level and high-level. This is based on how far the language is from the hardware (the Operating System, CPU, and RAM), as shown in the diagram below. 

Low-level vs. High-level Programming Languages
Figure 2. Low-level vs. High-level Programming Languages.

Everything that a computer does is done by the processor – the CPU. Every application that is started on your computer (including the OS) is given some time to talk to the CPU. However, at the lowest level, the CPU understands only machine-code instructions

Machine code is made of binary values represented by 0s and 1s. Each instruction tells the processor to perform a simple operation, such as moving a value into a register, adding two numbers, reading data from memory, or jumping to another instruction. You can check out the Intel x64 manual, just for example.

Additionally, there are multiple types of CPU architectures – Intel x86, Intel x64, AMD, Apple M chips, Apple A chips, Qualcomm, etc. Different processor architectures understand different machine-code instructions. For example, a program compiled for an Intel x86 processor cannot normally run directly on an Apple M4 chip and vice versa.

The operating system is also software. Its code is compiled into machine instructions that are executed by the CPU. Therefore, the operating system must also support the CPU architecture.

For example, an operating system built for the Apple M4 processor cannot normally be installed on a computer with an Intel x64 processor, and vice versa. The operating system manages the environment in which applications run. It:

  • loads the program into memory;
  • gives it CPU time;
  • manages RAM and I/O access;
  • prevents one program from accessing another program’s memory.

The operating system's scheduler divides CPU time into small time slices, typically lasting only a few milliseconds. It rapidly switches between active applications to give the illusion of simultaneous execution. You can imagine the CPU executing a loop where it says:

“App X, it is your turn to use the CPU. It will fetch your instructions from memory and run them.”

After a few milliseconds, the OS pauses that application and allows another to use the CPU, and so on and so on.

Lower-Level Programming Languages

The CPU is very exact. The CPU does not understand what a program is supposed to achieve. It simply follows the instructions it receives, in the exact order they are written, even when those instructions make little sense.

Low-level languages such as C are close to the computer hardware (the CPU and the OS). C allows programmers to give exact instructions to the CPU and the OS– for example, a programmer can tell the CPU:

“Store this variable at this exact address in memory”. 

This is extremely powerful if you want to write super-fast, hardware-optimized code such as operating systems, drivers, games, or other high-performance apps.

But on the other hand, this requires programmers who know exactly what they want to achieve and how the hardware works at a low level in order to achieve it. It also makes the syntax and structure of code much more complex and harder to read than other languages.

But we are network engineers, not software developers. 

We don’t know how the CPU works, what CPU registers are, what memory addresses are, and how everything fits together. We want to write code that loops through a list of devices and returns all configured VLANs. 

  • Do we care where the CPU stores the variables in memory? No, we don’t.
  • Do we care which registers the CPU jumps to? No, we don’t.

Well, that’s why the industry invented high-level programming languages like Python for people like us.

High-Level Programming Languages

High-level programming languages abstract the hardware away from the program code and manage it behind the scenes. By doing so, they can also make the syntax as close to native human language as possible. For example, you can probably understand what the following Python code does, even though the code is not a simple program and you may not have any prior Python experience. 

#!/usr/bin/env python3

device_list = ["R1", "R2", "SW1", "SW2", "SW3", "SW4", "SW5"]

router_count = 0
switch_count = 0

for device in device_list:
   if device.startswith("R"):
       print(device, "is a router")
       router_count = router_count + 1
   else:
       print(device, "is a switch")
       switch_count = switch_count + 1
       
print("Number of routers:", router_count)
print("Number of switches:", switch_count)

Keep in mind that the code is not simple at all. It includes a few different kinds of variables, if-else logic, objects, methods, and operators. However, even if you don’t have any prior Python experience, I am pretty sure you understand what it does 100%. Well, that’s one of the main advantages of high-level languages.

The program goes through a list of device names. If a name starts with R, the device is counted as a router. Otherwise, it is counted as a switch. At the end, the program displays the total number of routers and switches.

This code highlights another big advantage of high-level languages. They hide most hardware details from the programmer

  • Do you see me telling the CPU where to store the device_list variable in memory? No.
  • Do you see me telling the OS how memory is released when the variable is no longer used? No.
  • Do you see me telling the OS how individual characters are displayed on the screen? No.

Python Virtual Machine (PVM) handles these tasks for me behind the scenes. High-level languages like Python focus on the problem we want to solve rather than on the hardware that executes the solution.

Additionally, the code above can be executed on Intel x86, AMD, Qualcomm, or Apple M1 processors, and it will work the same. So high-level programming languages are typically cross-platform.

Analogy: Computer hardware as a restaurant

To better understand the concept, let’s make an analogy with something pretty well known. Let’s say that the computer hardware (specifically, the Operating System and the CPU) is a Restaurant, as shown in the diagram below:

The OS and the CPU as a restaurant
Figure 3. Computer hardware as a Restaurant.
  • The CPU is the cook.
  • The machine code is the detailed recipe that the cook understands.
  • The source code is the order written by the waiter.
  • The compiler is a kitchen manager who knows how to translate the order into an exact recipe.
  • The operating system (OS) is the restaurant.

The restaurant (the operating system) decides:

  • which order is prepared first;
  • which tools and ingredients are available;
  • how different cooks share the kitchen;

The cook can only cook strictly following an exact step-by-step recipe. The cook cannot take orders from clients directly.

Now imagine you are the waiter in this restaurant. A client wants to order a green salad. Writing in low-level language means that instead of writing “Give me a green salad” to the kitchen, you write “Wash and dry a cup of lettuce and spinach. Slice a cucumber into thin rounds. Cut ten cherry tomatoes in half. Add a spoon of olive oil. Add a tablespoon of salt…” and so on. 

You see the difference, right? For some tasks, it doesn’t make sense to go to a low level.

Compiled vs. Interpreted Programming Languages

We have already established that the CPU understands only machine-code instructions. It cannot directly understand Python, C, Java, or any other programming language. Consider the following Python code:

print("Hello, World!")

This instruction is clear to us, but it means nothing to the CPU. Before the processor can do anything with it, the instruction must pass through one or more translation layers.

Programming languages generally use two main approaches to perform this translation:

  • The program can be compiled before it runs.
  • The program can be interpreted while it runs.

These approaches describe how the language implementation translates the programmer's source code into machine code for execution. 

Compiled Programming Languages

In a compiled language, a program called a compiler translates the entire source code before the application is started. For example, imagine that you write a program in C. The processor cannot execute the C source code directly. You must first pass the source code to a C compiler. The process looks like this:

Entire C source code -> C compiler -> Entire source code into Machine code -> CPU

The compiler examines the complete program and translates it into machine-code instructions for a specific operating system and CPU architecture. The result is an executable file (for example, on Windows it is typically a .exe file).

When the user starts the application, the operating system loads the executable file into RAM and allocates CPU time to it. The processor can then execute its machine-code instructions directly.

Compiled languages focus more on early error detection, execution speed, and direct control over the final program.

Let’s return to our restaurant analogy.

Suppose the entire source code is the orders from all waiters from all tables in the restaurant. The compiler is the kitchen manager who takes all orders at once (the entire source code), translates them into one big, detailed recipe (the entire machine code), and then gives it to the cook to prepare.

With a compiled program, all orders are translated into a full recipe that is prepared before cooking begins. Once the translation is complete, the cook can follow the instructions without asking the kitchen manager to translate each step again.
This is one reason compiled programs can be very fast. Most of the translation work has already been completed.

Compiled vs. Interpreted Analogy
Figure x. Compiled vs. Interpreted Analogy.

Compilation also helps catch some mistakes before the program starts. For example, the compiler may detect invalid syntax, missing functions, or incorrect data types. The programmer must correct these problems before the compiler can create the executable file.

But if this is a real restaurant, would this approach be the best for the clients? Well, it depends. In most cases, not. But what if there is only one waiter? Would you prefer to wait for him to go to each table, then to the kitchen, and back to the next table? Or would you prefer him to take all orders from all tables and give them to the kitchen? Probably yes.

Neither approach is always better. It always depends on the use case. As it is always the case in networking as well (as you already know).

However, compilation has a trade-off.

Machine code is created for a particular platform. A program compiled for a Windows computer with an Intel x64 processor cannot normally run directly on a Mac with an Apple Silicon processor. The same source code may need to be compiled several times:

  • Source code -> Windows x64 executable
  • Source code -> Linux x64 executable
  • Source code -> MacOS Apple Silicon executable

The compilation process also takes time. For large source code, it can take minutes/hours. So, every time you change something, you need to wait for the code to compile before testing it.

Interpreted Programming Languages

An interpreted language uses a different approach, as you have already seen on the right side of the diagram. Instead of translating the complete source code into a standalone machine-code program before execution, an interpreter remains involved while the program is running (during runtime).

The interpreter reads the program’s instructions line by line (or part by part), processes them, and performs the required operations. A simplified view looks like this:

Python Source code -> CPython compiler -> Python bytecode -> PVM -> OS and CPU

In the restaurant analogy, the interpreter is like a kitchen manager who stays beside the cook while separate orders come in. The manager reads each order, prepares a recipe for it, and gives it to the cook. The kitchen manager remains involved until the order is complete.

This provides several advantages.

You can change the program and run it again immediately. You do not normally need to create a new executable file manually after every small change. This makes interpreted languages convenient for testing, automation, and rapid development.

The same source code can also run on different platforms, provided that a compatible interpreter is installed on each platform. For example, the following Python code can normally run without modification on Windows, Linux, and macOS:

device_list = ["R1", "R2", "SW1", "SW2"]
for device in device_list:
   print("Checking", device)

The Python code remains the same. What changes is the Python interpreter installed on the computer. The Windows version of the interpreter knows how to communicate with Windows. The Linux version knows how to communicate with Linux. The macOS version knows how to communicate with macOS.

The interpreter therefore acts as a middle layer between your Python program and the platform underneath it.

Python as an example of Interpreted Language
Figure x. Python as an example of an interpreted language.

The disadvantage is that the interpreter must perform some work while the program is running. This creates additional overhead compared with machine code that the CPU can execute directly.

Some errors may also remain hidden until the interpreter reaches the affected instruction. A program may start successfully, run for some time, and then stop because it reaches an invalid operation.

Notice that the Python Virtual Machine does not execute your clear-text Python source code directly. The interpreter first compiles the source code into an intermediate language form called bytecode.

Suppose you run this program:

print("Hello, world!")

First, CPython checks the syntax and converts the instruction into Python bytecode. Bytecode is lower-level than Python source code, but it is not the native machine code understood directly by the CPU.

The bytecode is then executed by the Python Virtual Machine, commonly shortened to PVM.

The PVM is part of the Python interpreter. It reads the bytecode instructions and performs the required operations. Many of those operations eventually call functions written in C, which communicate with the operating system.

The operating system then works with the hardware to display the text on your screen.

However, we still describe Python as interpreted because the Python interpreter and the PVM remain involved while the program is running. The bytecode is not usually distributed as a standalone native application that can run without Python.

Why Use Bytecode?

Bytecode gives Python an important advantage: portability.

A native machine-code instruction built for an Intel processor may not work on an Apple Silicon processor. However, Python bytecode is designed to be executed by the Python Virtual Machine rather than directly by a particular CPU.
Each platform has its own compatible CPython interpreter:

The same Python program -> Windows CPython interpreter -> Different OS
                           Linux CPython interpreter
                           MacOS CPython interpreter

This design allows the programmer to focus on the task rather than the platform.

As network engineers, we can write a script that connects routers, collects interface information, or checks configured VLANs. We do not need to create separate source-code versions for Intel, AMD, or Apple processors. The interpreter takes care of the platform-specific details.

The Main Trade-Off

To finish this part, let’s summarize that:

  • Compiled languages usually focus more on execution speed, early error detection, and direct control over the final program.
  • Interpreted languages usually focus more on flexibility, portability, faster development, and easier testing.

Neither approach is always better.

  • A compiled language may be the right choice for an operating system, device driver, high-performance, or real-time application.
  • An interpreted language such as Python may be the better choice for network automation, API integration, data processing, and administrative scripts.

Everything depends on the problem you are trying to solve. For network engineers, development speed and readable code are often more valuable than saving CPU time. This is one of the main reasons Python has become the dominant language for network automation.

Dynamically typed vs Statically typed languages

An additional important property of programming languages is whether the language is dynamically or statically typed.  The difference is mainly about when a programming language checks the data type of a variable.

In a dynamically typed language, the variable does not have a fixed type. The type belongs to the value stored in the variable and is checked while the program is running.

Python is dynamically typed:

Python
vlan = "Management"
print(vlan)
vlan = 10
print(vlan)

The same variable name, vlan, first refers to a string and then to an integer. Python allows this because the variable itself is not permanently defined as a string or an integer. In the next chapter, you will understand how this works under the hood.
The main advantage is flexibility. You can write code quickly without declaring the type of every variable. Python checks the type at runtime.

However, the downside is that at some point you lose track of the types of all variables in your code (because their types can change dynamically; a variable that is an integer can become a string at some point). Then at some point, you can try to add an integer with a string, which Python can’t do and crashes your code, as shown below.

Python
>>> x = 5
>>> y = "4"
>>> x + y
Traceback (most recent call last):
 File "<pyshell#2>", line 1, in <module>
   x + y
TypeError: unsupported operand type(s) for +: 'int' and 'str'

In a statically typed language, the type of a variable is usually declared or determined before the program runs. The variable is then expected to keep that type.

C#
string device = "Router1";
int vlan = 10;

Static typing catches many type-related mistakes during compilation or development.

I always think about statically and dynamically typed languages like switch ports. Imagine that a variable is a switchport. Is it neither access nor a trunk port, right? Its type is decided when you connect a device to the port. If you connect a server, it becomes an access port. If you connect a switch, it becomes a trunk.

In this context, a statically typed language is a switch with all ports preconfigured as either access or trunk ports before any device is connected. On the other hand, a dynamically typed language is a switch with all ports set to dynamic. The port type is determined dynamically when the device is connected. Additionally, you can disconnect the switch and connect a server, which changes the port from trunk to access. This is the analog of dynamically changing the type of variable.

Putting it all together

For me, the most practical way to understand computers is to see them as stacked layers of abstractions. Each layer hides much of the complexity of the layer below it and provides a simpler interface to the layer above.
Now, using this analogy, each Python program that you write will use the following stack, as shown in the diagram below.

Computer as stacked layers of abstraction
Figure x. Computer as stacked layers of abstraction.

Now in this context, let’s discuss some of the talking points about Python. People say Python is slow. Technically, it is slower than low-level languages, but … Let’s say you run a print() function in Python. When you run the code, what happens is that the CPython interpreter converts the print() function into bytecode and calls a pre-compiled C function to send the text to stdout (standard output). C code then asks the operating system to write the required characters to that terminal. The operating system manages the terminal application, graphics system, device drivers, and hardware resources. Eventually, the text is converted into the pixels that appear on your monitor.

So thinking about speed, from a Python perspective, the only expensive operations are the clear-text code input (print(“Hello World!”)) converted into bytecode, which takes nanoseconds. The actual printing of the text to the terminal uses a C-based library, and if you had written the code in C, it would have run the exact same C-based code. Essentially, Python "borrows" the speed of C by calling those pre-compiled C functions, but you get the ease-of-use syntax and dynamic typing.

Looking at the stack, also notice that Python requires you to install the CPython interpreter in order to execute Python code. Some operating systems come with CPython preinstalled, but most popular ones do not (like Windows).

Why has Python become so popular?

So you have seen some of the most distinctive properties of programming languages. Since there are many languages out there, there are almost all combinations of properties. For example, there are languages that are high-level but compiled (not interpreted) and statically typed – for example, Go. 

Every programming language design choice makes it better at something and worse at other things. For example, Go is much more stable and safer to use because it is statically typed and compiled. However, the trade-off is that it is slower and less flexible to write. Also, the fact that it is compiled means it must be pre-compiled for different operating systems and CPUs. It also has fewer community codes to reuse.

On the other hand, Python is super flexible to read and write and cross-platform. However, more errors can appear at runtime, potentially crashing your program because it is not compiled and statically typed. It also requires you to install CPython on all platforms that you intend to use it on.

Everything is a trade-off.

However, for many people, Python sits at the sweet spot, as shown in the diagram below. 

Why Python became so popular?
Figure x. Why has Python become so popular?


Figure 2.1.xxx.xx

Python Versions

Python continues to develop through new versions that add features, improve performance, and fix security problems. A version such as Python 3.14.2 has three parts: the major version, the feature version, and the bug-fix version.
The most important difference is between Python 2 and Python 3. Python 2 is outdated and unsupported since 2020, so all new programs should use Python 3.

Different computers may still have different Python 3 versions installed. Newer language features or libraries may not work with older versions, so it is useful to check your version with:

Microsoft Windows [Version 10.0.26200.8655]
(c) Microsoft Corporation. All rights reserved.

C:\Users\ivan> python --version
Python 3.13.14

Throughout this book, we will use Python 3 and focus on features that work across common supported versions.
Other Python Interpreters – IronPython, Jython, PyPy

So far, we have discussed CPython because it is the most widely used Python interpreter. It is the standard implementation that most people install when they download Python from the official Python website. However, CPython is not the only interpreter that can execute Python code.

Python is a programming language, while CPython is one implementation of the interpreter layer of that language. However, some software developers realized that they already have an interpreter layer in their infrastructure that they can use to run the same basic Python syntax, but the work behind the scenes may be handled in a different way.

For example, if you are a Java developer, you already have the Java Virtual Machine pre-installed, which converts Java ByteCode into machine code. Hence, you can take advantage of that. That’s how Jython was created. Jython is a Python implementation designed to work with Java. Instead of running Python bytecode through the standard CPython Virtual Machine, Jython converts the code into Java bytecode and runs it on the Java Virtual Machine, or JVM. This allows Python programs to use Java classes and libraries more directly.

Jython and JVM
Figure x. Jython and JVM.

IronPython follows a similar idea but is designed for Microsoft’s .NET platform. It runs Python code using the .NET Common Language Runtime, or CLR. This makes it useful when a Python application needs to interact closely with C#, PowerShell, or other .NET technologies.

Another well-known implementation is PyPy. PyPy is designed mainly to improve the performance of Python programs. It includes a Just-In-Time compiler, commonly called a JIT compiler. The JIT watches the program while it runs and converts frequently used parts into native machine code. Because of this, some long-running Python programs can run much faster with PyPy than with CPython.

Each interpreter has its own purpose, advantages, and limitations. Jython works well with Java, IronPython works well with .NET, PyPy focuses on performance, and MicroPython focuses on small devices. However, CPython remains the most common choice because it provides the widest support for Python libraries, tools, learning resources, and third-party packages.

For most network automation tasks, we will use CPython. Libraries such as Netmiko, NAPALM, Paramiko, and Requests are commonly developed and tested with it. Still, knowing that other interpreters exist helps us understand an important point: Python is the language, while CPython is only one of the programs capable of executing that language.