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!

DevAsc – Python Classes
Tagged on:             

2 thoughts on “DevAsc – Python Classes

  • June 26, 2020 at 6:08 pm
    Permalink

    Nicely written Daniel._init_ has always been a mystery for me😊

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *