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__
__init__ and
self
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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"
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"
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
class in your code. Class names are often written in camel case, such as
NetworkDevice
NetworkDevice, where each word has an upper case letter.

When it comes to classes, functions within classes are called

methods
methods. A class must always contain some code. This means that the following is not acceptable:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class NetworkDevice:
class NetworkDevice:
class NetworkDevice:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
daniel@devasc:~/DevAsc$ python3 networkdevice.py
File "networkdevice.py", line 3
^
SyntaxError: unexpected EOF while parsing
daniel@devasc:~/DevAsc$ python3 networkdevice.py File "networkdevice.py", line 3 ^ SyntaxError: unexpected EOF while parsing
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
pass to indicate to Python that nothing needs to be executed.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class NetworkDevice:
pass
class NetworkDevice: pass
class NetworkDevice:
    pass
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
daniel@devasc:~/DevAsc$ python3 networkdevice.py
daniel@devasc:~/DevAsc$
daniel@devasc:~/DevAsc$ python3 networkdevice.py daniel@devasc:~/DevAsc$
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
attributes for the class. First we must understand what an
instance
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
self. Leveraging self, we can access the
attributes
attributes and
methods
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__
__init__

Let’s create the NetworkDevice class again with

__init__
__init__ and go through some common errors, and do some more explaining.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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")
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")
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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
self is always passed by Python automatically? We didn’t define
self
self in the
__init__
__init__ method. Let’s fix that and add a print statement:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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)
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)
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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'
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'
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__
__init__ method, we didn’t refer to
self
self, so the attributes didn’t get initiated properly. Let’s fix that!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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)
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)
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)     
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
daniel@devasc:~/DevAsc$ python3 networkdevice.py
192.0.2.1
daniel@devasc:~/DevAsc$ python3 networkdevice.py 192.0.2.1
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
daniel@devasc:~/DevAsc$ python3 networkdevice.py
essentials
daniel@devasc:~/DevAsc$ python3 networkdevice.py 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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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())
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())
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
daniel@devasc:~/DevAsc$ python3 networkdevice.py
no
yes
daniel@devasc:~/DevAsc$ python3 networkdevice.py no yes
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 *