Skip to main content
PacketMentor logo
Open menu
← All topics
Automation & Programmability Foundational

Python: Functions, Classes and Modules

The three building blocks every Python network script grows into: functions (reusable blocks), classes (grouped state and behavior), modules (grouped files). When to use each, with a Cisco-flavored example of each.

Quick summary
  • Function: a named block of code you can call repeatedly. Use it the moment you copy-paste the same lines twice.
  • Class: a container for related state and behavior. Use it when the data a function needs is always the same bundle (e.g. a Device with host, user, pass, platform).
  • Module: a .py file you can import from another .py file. Use it the moment a script is big enough that scrolling to find a function is annoying.

Mental model

A 20-line script fits in your head. A 500-line script does not. The difference between the two is how you organise the code: into functions, classes and modules.

Cisco objective 1.5 (CCNAAUTO 200-901) asks you to explain the benefits of organising code into these three building blocks. This topic is the explanation.

The rule of thumb for each:

  • A function replaces repetition. The moment you copy-paste the same lines twice, extract a function.
  • A class replaces “pass this same bundle of 5 arguments to every function”. Group the state and the behavior together.
  • A module replaces one giant file. Split by responsibility (one file for parsing, one for API calls, one for the main loop).

Functions: reusable blocks

Before (repetitive)

from netmiko import ConnectHandler

dev1 = {"device_type": "cisco_ios", "host": "10.0.0.1", "username": "admin", "password": "x"}
with ConnectHandler(**dev1) as c:
    print(c.send_command("show version | i Cisco IOS"))

dev2 = {"device_type": "cisco_ios", "host": "10.0.0.2", "username": "admin", "password": "x"}
with ConnectHandler(**dev2) as c:
    print(c.send_command("show version | i Cisco IOS"))

dev3 = {"device_type": "cisco_ios", "host": "10.0.0.3", "username": "admin", "password": "x"}
with ConnectHandler(**dev3) as c:
    print(c.send_command("show version | i Cisco IOS"))

After (one function)

from netmiko import ConnectHandler

def show_version(host, username="admin", password="x"):
    dev = {"device_type": "cisco_ios", "host": host, "username": username, "password": password}
    with ConnectHandler(**dev) as c:
        return c.send_command("show version | i Cisco IOS")

for h in ["10.0.0.1", "10.0.0.2", "10.0.0.3"]:
    print(show_version(h))

Benefits:

  • Readable. The main loop reads like English: for each host, show its version.
  • Changeable in one place. Switch to show version | i Software once, every caller gets it.
  • Testable. You can call show_version("10.0.0.1") from a unit test with a mocked ConnectHandler.
  • Default arguments keep the common case short while allowing overrides.

Rules of thumb for functions

  • A function should do one thing. If you cannot name it in a sentence, split it.
  • 20 lines of body is a soft limit. Longer usually means two things in one function.
  • Return a value instead of printing. The caller decides what to do with it (print, write to a file, send to an API).

Classes: grouped state and behavior

Use a class when the same bundle of data (host + user + password + platform) is passed into many functions, OR when there is state that persists between calls (an open SSH connection, a running counter).

Before (loose functions, repeated bundle)

def show_version(host, user, password, platform): ...
def show_run(host, user, password, platform): ...
def push_config(host, user, password, platform, lines): ...

# Every call repeats the bundle:
show_version("10.0.0.1", "admin", "x", "cisco_ios")
show_run("10.0.0.1", "admin", "x", "cisco_ios")
push_config("10.0.0.1", "admin", "x", "cisco_ios", ["interface gi0/0", "description WAN"])

After (one class)

from netmiko import ConnectHandler

class Device:
    def __init__(self, host, user="admin", password="x", platform="cisco_ios"):
        self.host = host
        self.user = user
        self.password = password
        self.platform = platform

    def _conn(self):
        return ConnectHandler(
            device_type=self.platform, host=self.host,
            username=self.user, password=self.password,
        )

    def show_version(self):
        with self._conn() as c:
            return c.send_command("show version | i Cisco IOS")

    def push_config(self, lines):
        with self._conn() as c:
            return c.send_config_set(lines)

# Usage:
d = Device("10.0.0.1")
print(d.show_version())
d.push_config(["interface gi0/0", "description WAN"])

Benefits:

  • One place to construct the bundle (Device("10.0.0.1")).
  • Methods know their state without the caller passing it every time.
  • Easy to subclass when a new platform needs small overrides (e.g. class CatalystCenter(Device) with its own _conn).

When NOT to use a class

  • The function has no state and never shares its arguments with other functions. Keep it a function.
  • Only one instance will ever exist. A module with top-level functions is simpler.

Modules: grouped files

A module is just a .py file you can import from another .py. Split a script into modules when:

  • The file is more than ~300 lines.
  • You have clearly separable responsibilities (one for parsing, one for API, one for CLI).
  • Multiple scripts need the same functions.

Example layout

net-tools/
├── __init__.py            # (makes net-tools a Python package; can be empty)
├── device.py              # the Device class above
├── parsers.py             # show-command output parsers
├── api_meraki.py          # Meraki Dashboard API wrapper
└── cli.py                 # the main() that parses argv and dispatches

From cli.py you can:

from net_tools.device import Device
from net_tools.parsers import parse_show_interface

Benefits:

  • Find anything fast. You know where parsers live, where device classes live.
  • Import only what you need. If a script does not touch Meraki, it does not pay the cost of loading that module.
  • Reusable. A second script just imports what it needs; no copy-paste.
  • Easier to test. You can test parsers.py in isolation without needing a device connection.

When to apply each

You find yourself…Reach for…
copying 5 lines into a second placea function
passing the same 4 arguments into every functiona class
scrolling for 10 seconds to find a functionsplit into modules
writing tests for a 300-line filesplit into modules; tests get easier

The #1 mistake

Making a class because the tutorial you read used classes. If you are writing a 50-line script that connects to one device and prints one show-command output, you do not need a class. Just a function or two. Classes have a cost: more boilerplate, more concepts, harder to grok at a glance. Only pay that cost when the state/bundle problem actually appears.

FAQ

What is self? The first parameter of every method inside a class. Python passes the instance in automatically when you call d.show_version(). You never type self at the call site; you type it in the method definition.

What is __init__? The special method Python calls when you create an instance (Device("10.0.0.1")). Use it to store initial state on self.

Do I need to put every function in a class? No. If a function has no related state, keep it a top-level function in a module.

What is __name__ == "__main__" for? A guard so that code only runs when the file is executed directly (python cli.py), not when it is imported from another file. The pattern:

def main():
    ...

if __name__ == "__main__":
    main()

What is a package? A folder with an __init__.py file in it. Treated by Python as an importable unit. The net-tools/ layout above is a package.

Master this on a real network

Want this drilled into reflex?

1:1 weekly sessions, live feedback on your labs, and US interview prep: built around the CCNA Automation® exam blueprint. Free first session. No card on file until you decide.

Claim my free session →

Get the free CCNA 12-week roadmap

You're already reading up on Python: Functions, Classes and Modules. The roadmap is the order I recommend studying every CCNA topic in: with what to lab each week and where Python: Functions, Classes and Modules fits. A written personal reply, not an autoresponder. Expect it within one business day.

Personal reply from a senior network engineer. No third-party tracking. Unsubscribe any time.