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