Python classes are very useful when you need to create objects with the same characteristics. This is often referred to as Object Oriented Programming (OOP). Not having much of a programming background, I found classes to be a bit confusing, and I wasn’t fully understanding the use of __init__
and self
. Thanks to the Twitter community, my friend Peter Palúch , and the videos of Cory Schafer, I know feel I have a better understanding, and wanted to share my findings, from a networking person’s perspective.
First, let’s look at why classes are needed in the first case. Let’s say that we want to keep track of our network devices. The attributes we are interested in are:
- Hostname
- Vendor
- Device type
- Model
- Loopback
We can of course create this information manually, without classes, like this:
daniel@devasc:~/DevAsc$ python3 Python 3.8.2 (default, Apr 27 2020, 15:53:34) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> device1_hostname = "r1" >>> device1_vendor = "Cisco" >>> device1_type = "router" >>> device1_model = "ISR4331" >>> device1_loopback = "192.0.2.1" >>> device2_hostname = "sw1" >>> device2_vendor = "Cisco" >>> device2_type = "switch" >>> device2_model = "Cat9300" >>> device2_loopback = "198.51.100.1"
However, this becomes very repetitive, it’s error prone, we’ll end up using a ton of variables, and it’s not very modular or extensible, is it? This would be better handled using a class.
Classes are indicated by the word class
in your code. Class names are often written in camel case, such as NetworkDevice
, where each word has an upper case letter.
When it comes to classes, functions within classes are called methods
. A class must always contain some code. This means that the following is not acceptable:
class NetworkDevice:
daniel@devasc:~/DevAsc$ python3 networkdevice.py File "networkdevice.py", line 3 ^ SyntaxError: unexpected EOF while parsing
If you want to use a class, but you don’t have the code for the class yet, you can use pass
to indicate to Python that nothing needs to be executed.
class NetworkDevice: pass
daniel@devasc:~/DevAsc$ python3 networkdevice.py daniel@devasc:~/DevAsc$
The error is now gone.
Great, we know how to create a class but we also want to define attributes
for the class. First we must understand what an instance
is. A class is like a template. We are creating a class for network devices and we want to set some attributes. When we create a new network device, using the class, we are creating a new instance of that class. Although each instance has the same type of attributes, each device is unique.
In Python, each instance is represented by self
. Leveraging self, we can access the attributes
and methods
of that instance. Self is always passed into the methods of a class, whether we have specified it as an argument or not. This is some Python magic. We’ll come back to this soon.
When using a class, we must initialize the attributes of the instance of the class. This is in some other languages called a constructor. In Python, we do this by using the special method called __init__
.
Let’s create the NetworkDevice class again with __init__
and go through some common errors, and do some more explaining.
class NetworkDevice: def __init__(nwhostname, nwvendor, nwtype, nwmodel, nwloopback): nwhostname = nwhostname nwvendor = nwvendor nwtype = nwtype nwmodel = nwmodel nwloopback = nwloopback device1 = NetworkDevice("r1", "Cisco", "router", "ISR4331", "192.0.2.1")
When we run this code, this is what we get:
daniel@devasc:~/DevAsc$ python3 networkdevice.py Traceback (most recent call last): File "networkdevice.py", line 9, in <module> device1 = NetworkDevice("r1", "Cisco", "router", "ISR4331", "192.0.2.1") TypeError: __init__() takes 5 positional arguments but 6 were given
Oops! 6 arguments? Remember we said that self
is always passed by Python automatically? We didn’t define self
in the __init__
method. Let’s fix that and add a print statement:
class NetworkDevice: def __init__(self, nwvendor, nwtype, nwmodel, nwloopback): nwhostname = nwhostname nwvendor = nwvendor nwtype = nwtype nwmodel = nwmodel nwloopback = nwloopback device1 = NetworkDevice("r1", "Cisco", "router", "ISR4331", "192.0.2.1") print(device1.nwloopback)
Let’s run the code again:
daniel@devasc:~/DevAsc$ python3 networkdevice.py Traceback (most recent call last): File "networkdevice.py", line 10, in <module> print(device1.nwloopback) AttributeError: 'NetworkDevice' object has no attribute 'nwloopback'
Oops again! When we defined our attributes in the __init__
method, we didn’t refer to self
, so the attributes didn’t get initiated properly. Let’s fix that!
class NetworkDevice: def __init__(self, nwhostname, nwvendor, nwtype, nwmodel, nwloopback): self.nwhostname = nwhostname self.nwvendor = nwvendor self.nwtype = nwtype self.nwmodel = nwmodel self.nwloopback = nwloopback device1 = NetworkDevice("r1", "Cisco", "router", "ISR4331", "192.0.2.1") print(device1.nwloopback)
daniel@devasc:~/DevAsc$ python3 networkdevice.py 192.0.2.1
Better! We can also define other methods that could be useful. For example, maybe we want to know if a device supports BGP. We first extend our class to support setting the license, if no license is set, it defaults to essentials.
class NetworkDevice: def __init__(self, nwhostname, nwvendor, nwtype, nwmodel, nwloopback, nwlicense = "essentials"): self.nwhostname = nwhostname self.nwvendor = nwvendor self.nwtype = nwtype self.nwmodel = nwmodel self.nwloopback = nwloopback self.nwlicense = nwlicense
We then see that the default is essentials:
daniel@devasc:~/DevAsc$ python3 networkdevice.py essentials
We write a method to check if the device supports BGP. If the license is essentials, it does not:
class NetworkDevice: def __init__(self, nwhostname, nwvendor, nwtype, nwmodel, nwloopback, nwlicense = "essentials"): self.nwhostname = nwhostname self.nwvendor = nwvendor self.nwtype = nwtype self.nwmodel = nwmodel self.nwloopback = nwloopback self.nwlicense = nwlicense def supports_bgp(self): if self.nwlicense == "essentials": return "no" else: return "yes" device1 = NetworkDevice("r1", "Cisco", "router", "ISR4331", "192.0.2.1") device2 = NetworkDevice("r2", "Cisco", "router", "ISR4331", "192.0.2.2", "advantage") print(device1.supports_bgp()) print(device2.supports_bgp())
If we run the code, this is what it looks like:
daniel@devasc:~/DevAsc$ python3 networkdevice.py no yes
We could leverage this information for a configuration workflow to only configure BGP on devices that are licensed for it.
I hope this has been helpful! See you next time!
Nicely written Daniel._init_ has always been a mystery for me😊
Thanks!