Cisco DevNet Associate course 013 Software Development and Design Explain the benefits of organizing code into methods / functions
Watch Full Demo on YouTube:
Introduction:
In Python, functions are one of the most important building blocks for creating clean, efficient, and reusable code. They allow you to group related code into a single, reusable unit, making your programs more organized and easier to maintain. Whether you are working on a small script or a large-scale application, understanding how functions work — and the different types you can use — is essential for writing professional Python code.
💡 What is a Function in Python?
A function in Python is a reusable block of code designed to perform a specific task. Instead of writing the same instructions multiple times, you can define a function once and call it whenever needed. This helps avoid repetition, reduces errors, and makes code easier to read.
Functions can take parameters (inputs), perform operations, and optionally return a result (output). They are defined using the def
keyword followed by a name, parameters in parentheses, and a block of indented code.
Basic Python Function
Let’s start with a simple example:
def greet(name):
"""Return a greeting message for the given name."""
message = "Hello, " + name + "!"
return message
# Calling the function
print(greet("Amina"))
print(greet("Sarah"))
Step-by-step breakdown:
def greet(name):
– We use thedef
keyword to define a new function calledgreet
that takes one parameter,name
.message = "Hello, " + name + "!"
– Inside the function, we create amessage
by combining text with the value passed inname
.return message
– The function sends the greeting back to the caller.- Function calls – We call
greet("Amina")
andgreet("Sarah")
, printing the returned greetings.
Output:
Hello, Amina!
Hello, Sarah!
📊 Types of Functions in Python
Python offers several types of functions, each serving different purposes:
1. Built-in Functions
Functions that come pre-installed with Python, such as:
print()
– Displays output.len()
– Returns the length of a sequence.type()
– Shows the data type of a value.
Example:
ips = ["10.0.0.1", "10.0.0.2"]
print("Count:", len(ips))
print("First type:", type(ips[0]))
2. User-Defined Functions
Functions that you create yourself to perform specific tasks.
Example:
def get_device_info(hostname, ip, platform='cisco_ios'):
return {'hostname': hostname, 'ip': ip, 'platform': platform}
device = get_device_info('core-switch', '10.10.10.1')
print(device)
This is how you package domain logic (e.g., building device records) so it’s reusable and testable.
3. Lambda Functions
One-line, unnamed functions made with lambda
, useful for short operations passed into higher-level functions.
Example:
interfaces = ['Gig0/12', 'Gig0/1', 'Gig0/2']
interfaces.sort(key=lambda s: int(s.split('/')[-1]))
print(interfaces)
The lambda
converts the string Gig0/12
to the integer 12
for numerical sorting. Lambdas are great for sort
, map
, filter
.
Caveat: Keep them short — for complex logic use a regular function.
4. Recursive Functions
Functions that call themselves to solve problems that can be broken down into smaller versions of the same problem.
Example:
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5)) # Output: 120
Recursion is elegant for tree traversals or divide-and-conquer algorithms. Always ensure there’s a base case to prevent infinite recursion.
5. Generator Functions
Functions that use yield
to produce a sequence of values lazily, one at a time, saving memory
Example:
def interface_generator(prefix, count):
for i in range(1, count+1):
yield f"{prefix}{i}"
for iface in interface_generator("Gig0/", 5):
print("Configuring", iface)
Instead of creating a full list of interface names, the generator yields each one when needed. This matters when handling large data streams (device lists, logs).
6. Nested Functions
Functions defined inside other functions, used for encapsulation and closure usage.
Example:
def configure_vlan(device_ip):
def connect():
print(f"Connecting to {device_ip}")
return True
def apply(vlan, name):
print(f"Applying VLAN {vlan} ({name})")
if connect():
apply(10, "MGMT")
configure_vlan("192.168.1.1")
Nested functions help keep helper logic private to the outer function and can capture variables from the outer scope (device_ip
) — that’s a closure.
7. Higher-order Functions (Decorators included)
Functions that accept other functions as arguments or return functions. Python decorators are a common pattern built on this.
Example:
def log_operation(fn):
def wrapper(*args, **kwargs):
print(f"Calling {fn.__name__} with {args} {kwargs}")
return fn(*args, **kwargs)
return wrapper
@log_operation
def backup_config(device):
print("Backing up", device)
backup_config("sw1")
The log_operation
function returns wrapper
, a new function that adds logging around backup_config
. Higher-order functions let you add behaviour (logging, retry, caching) without modifying the original function.
8. Class Methods and Static Methods
Methods inside classes that operate on the class (@classmethod
) or are utility functions related to the class (@staticmethod
). Instance methods operate on objects.
Example:
class NetworkDevice:
def __init__(self, hostname, ip):
self.hostname = hostname
self.ip = ip
def ping(self, target):
"""Instance method to ping from this device"""
print(f"Pinging {target} from {self.hostname} ({self.ip})")
return True
@classmethod
def from_csv(cls, csv_string):
"""Alternative constructor from CSV"""
hostname, ip = csv_string.split(',')
return cls(hostname, ip)
@staticmethod
def validate_ip(ip):
"""Static method to validate IP"""
parts = ip.split('.')
return len(parts) == 4 and all(0 <= int(p) <= 255 for p in parts)
# Usage
device1 = NetworkDevice('router1', '192.168.1.1')
device1.ping('8.8.8.8')
from_csv
is an alternative constructor; validate_ip
is a utility that logically groups with NetworkDevice
. Class methods are important for alternate construction patterns and class-level operations.
9. Coroutine Functions (async/await)
Asynchronous functions defined with async def
that use await
to pause for I/O without blocking the entire program. Great for concurrent network I/O.
Example:
import asyncio
async def check(device):
print("Checking", device)
await asyncio.sleep(1) # non-blocking
print(device, "OK")
async def main():
await asyncio.gather(*(check(d) for d in ["r1","sw1","fw1"]))
asyncio.run(main())
All three checks run concurrently (not serially), making async ideal for many simultaneous network requests.
When to prefer: High-latency I/O tasks: SSH sessions, HTTP API polling, SNMP queries done at scale.
10. Partial Functions (functools.partial
)
Functions created by pre-filling some arguments of an existing function using partial
, producing convenience wrappers.
Example:
from functools import partial
def configure(device, interface, config):
print(device, interface, config)
cfg_g0_1 = partial(configure, "router1", "Gig0/1")
cfg_g0_1("description uplink")
cfg_g0_1
is now a simplified function that already knows the device and interface; you only pass the config
.
When to prefer: When you repeatedly call a function with the same args — partials reduce duplication and make code simpler.
🧾 Conclusion
Functions are the backbone of well-structured Python code. They allow you to break complex problems into smaller, reusable chunks, making your programs easier to understand, maintain, and expand. By mastering both built-in and user-defined functions — as well as specialized types like lambdas, recursion, and class methods — you’ll be equipped to write more efficient and professional Python applications.
References:
Cisco Certified DevNet Expert (v1.0) Equipment and Software List