Python Classes and Objects: A Practical Guide to Object-Oriented Programming
Python treats every data type as an object derived from a specific class. Integers, strings, collections, and booleans are all instances of built-in classes. You can verify this by inspecting the type of any variable.
>>> count = 42
>>> type(count)
<class 'int'>
>>> label = "sample"
>>> type(label)
<class 'str'>
>>> flag = False
>>> type(flag)
<class 'bool'>
>>> items = []
>>> type(items)
<class 'list'>
>>> mapping = {}
>>> type(mapping)
<class 'dict'>
Defining and Instantiating Classes
Use the class keyword followed by a PascalCase identifier and a colon to define a blueprint. Instantiation creates a concrete object from that blueprint.
class Device:
pass
router = Device()
print(router)
# Output: <__main__.Device object at 0x...>
The Constructor Method
A class without initialization logic has limited utility. Python uses the __init__ method to set up initial state. The first parameter, conventionally named self, binds the method to the specific instance being created.
class Device:
def __init__(self, model_name):
self.model_name = model_name
switch = Device("Cisco-9300")
print(switch.model_name)
Expanding the constructor to accept multiple parameters:
class Device:
def __init__(self, brand, model, region, datacenter):
self.brand = brand
self.model = model
self.region = region
self.datacenter = datacenter
server = Device("Dell", "R740", "US-East", "DC-01")
print(server.brand, server.model, server.region, server.datacenter)
Instance Methods
Methods are functions defined inside a class that operate on instance data. They automatically receive self as the first argument.
class Device:
def __init__(self, hostname, region, datacenter):
self.hostname = hostname
self.region = region
self.datacenter = datacenter
def get_status(self):
return f"Node {self.hostname} is active in {self.region}/{self.datacenter}"
node_a = Device("web-01", "EU-West", "Frankfurt")
print(node_a.get_status())
Default Parameter Values
Assigning defaults in __init__ prevents instantiation errors when arguments are omitted and provides sensible fallback configurations.
class Device:
def __init__(self, hostname="default-node", region="Global", datacenter="Primary"):
self.hostname = hostname
self.region = region
self.datacenter = datacenter
def get_status(self):
return f"Node {self.hostname} is active in {self.region}/{self.datacenter}"
generic = Device()
print(generic.get_status())
custom = Device("api-gateway", "AP-South", "Mumbai")
print(custom.get_status())
Modifying State via Methods
Classes often manage mutable state. Methods can safely update internal attributes, such as appending to a list.
class Device:
def __init__(self, hostname="core-router", region="US"):
self.hostname = hostname
self.region = region
self.enabled_modules = []
def get_status(self):
return f"{self.hostname} ({self.region}) | Modules: {self.enabled_modules}"
def enable_module(self, module_name):
self.enabled_modules.append(module_name)
router_x = Device()
print(router_x.get_status())
router_x.enable_module("BGP")
router_x.enable_module("OSPF")
router_x.enable_module("NetFlow")
print(router_x.enabled_modules)
Inheritance
Inheritance enables a new class to acquire attributes and methods from an existing base class. The derived class can extend or specialize the base behavior.
class Server(Device):
pass
web_srv = Server("web-prod-01", "US-West")
print(web_srv.get_status())
web_srv.enable_module("Nginx")
web_srv.enable_module("Docker")
print(web_srv.enabled_modules)
Even without an explicit __init__ in Server, it inherits the parent's constructor and methods. To extend initialization, invoke super().
Overriding Parent Methods
Subclasses can replace parent implementations by defining a method with the identical name. Using super() allows the child to reuse parent initialization logic while adding its own attributes.
class Server(Device):
def __init__(self, hostname="app-srv", region="US", os_type="linux"):
self.os_type = os_type
super().__init__(hostname, region)
def get_status(self):
os_label = "Linux" if self.os_type == "linux" else "Windows"
return f"[{os_label}] {self.hostname} operational in {self.region}."
linux_box = Server("db-primary", "EU-Central", "linux")
print(linux_box.get_status())
linux_box.enable_module("PostgreSQL")
linux_box.enable_module("Replication")
print(linux_box.enabled_modules)
win_box = Server("ad-controller", "US-East", "windows")
print(win_box.get_status())
win_box.enable_module("ActiveDirectory")
win_box.enable_module("DNS")
print(win_box.enabled_modules)
The overridden get_status method now incorporates OS-specific formatting while retaining the inherited enable_module functionality.
Practice Exercises
Level 1
- Python includes a
statisticsmodule, but building one manually reinforces function design and code reuse. Implement aDataAnalyzerclass that computes central tendency (mean, median, mode) and dispersion (variance, standard deviation) for a numeric dataset. Include methods to retrieve the minimum, maximum, sample size, and arbitrary percentiles. All calculations should be encapsulated as instance methods.
Level 2
- Design a
Walletclass with attributes for owner name, income ledger, and expense ledger. Implement methods namedtotal_income,total_expense,summary,record_income,record_expense, andcurrent_balance. Structure the logic to track financial transactions and compute the remaining balance dynamically.