Python Classes
Classes, inheritance, dataclasses, and structural typing with protocols.
Class Fundamentals
Minimal class definition — creates a class with no attributes or methods
class Device:
pass
Constructor — self is the instance, store attributes here, type hints are documentation
def __init__(self, hostname: str, ip: str):
self.hostname = hostname
self.ip = ip
Developer representation — unambiguous, should ideally be valid Python to recreate the object
def __repr__(self) -> str:
return f"Device({self.hostname!r})"
User-friendly string — used by print() and f-strings, human-readable format
def __str__(self) -> str:
return f"{self.hostname} ({self.ip})"
Equality comparison — also implement hash if instances will be dict keys or in sets
def __eq__(self, other):
return self.ip == other.ip
Properties
Property getter — access as device.status not device.status(), encapsulates logic
@property
def status(self) -> str:
return self._status
Property setter — validates or transforms on assignment, device.status = "active" triggers it
@status.setter
def status(self, value: str):
if value not in ("active", "disabled", "maintenance"):
raise ValueError(f"Invalid status: {value}")
self._status = value
Inheritance
Inheritance with super — calls parent init, then adds child-specific attributes
class Switch(Device):
def __init__(self, hostname, ip, ports):
super().__init__(hostname, ip)
self.ports = ports
Abstract base class — forces subclasses to implement authenticate, cannot instantiate ABC directly
from abc import ABC, abstractmethod
class Authenticator(ABC):
@abstractmethod
def authenticate(self) -> bool:
...
Multiple inheritance — Python supports it, use sparingly, MRO determines method resolution
class ISENode(Device, Authenticatable):
pass
Class and Static Methods
Class method / alternate constructor — cls is the class itself, enables Device.from_csv(line)
@classmethod
def from_csv(cls, line: str) -> "Device":
return cls(*line.split(","))
# Usage: device = Device.from_csv("sw-core-01,10.50.1.10")
Static method — no self or cls, logically belongs to the class but doesn’t need instance state
@staticmethod
def is_valid_ip(ip: str) -> bool:
parts = ip.split(".")
return len(parts) == 4 and all(0 <= int(p) <= 255 for p in parts)
Dataclasses
Dataclass — auto-generates init, repr, eq from annotated fields
from dataclasses import dataclass
@dataclass
class Endpoint:
hostname: str
ip: str
port: int = 443
Frozen dataclass — immutable after creation, hashable, use for config objects and dict keys
@dataclass(frozen=True)
class Config:
hostname: str
port: int
Mutable default in dataclass — default_factory prevents shared mutable state between instances
from dataclasses import field
@dataclass
class Switch:
hostname: str
interfaces: list[str] = field(default_factory=list)
Post-init processing — runs after auto-generated init, compute derived attributes here
def __post_init__(self):
self.fqdn = f"{self.hostname}.{self.domain}"
Dunder Methods (Magic Methods)
len — makes len(switch) work, implement when object has countable contents
def __len__(self):
return len(self.interfaces)
iter — makes for intf in switch: work, delegate to internal collection
def __iter__(self):
return iter(self.interfaces)
contains — makes "Gi1/0/1" in switch work
def __contains__(self, item):
return item in self.interfaces
getitem — makes switch["Gi1/0/1"] or switch[0] work
def __getitem__(self, key):
return self.interfaces[key]
Context Manager Protocol
Context manager class — enables with DeviceManager() as dm: for resource management
class DeviceManager:
def __enter__(self):
self.session = connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.session.close()
return False # don't suppress exceptions
Structural Typing and Slots
Protocol (structural typing) — any class with matching methods satisfies it, no inheritance needed
from typing import Protocol
class Authenticatable(Protocol):
def auth(self) -> bool: ...
# Any class with an auth() -> bool method satisfies this
slots — restricts attributes, reduces memory ~40%, faster attribute access, no dict
class Device:
__slots__ = ("hostname", "ip", "port")
def __init__(self, hostname, ip, port=443):
self.hostname = hostname
self.ip = ip
self.port = port
Runtime Type Checking
isinstance and issubclass — isinstance checks instance, issubclass checks class hierarchy
isinstance(obj, Device) # True if obj is a Device or subclass
issubclass(Switch, Device) # True if Switch inherits from Device