Chapter 12 of 21
Exception Handling & Defenses
Current Study Module
OOP: Classes & Objects
OOP: Inheritance & Polymorphism
Chapter 11 • Object-Oriented Architecture

Object-Oriented Programming: Classes, Objects & Dunder Methods

Python is an object-oriented language from its core. Learn what the explicit self parameter actually represents in CPython, how to differentiate instance attributes from shared class attributes, when to use@classmethod and @staticmethod, and how to harness magic dunder methods.

1. Anatomy of a Class: Why "self" is Explicit

In C++ or Java, the this pointer is implicitly passed behind the scenes. In Python, adhering to the principle "Explicit is better than implicit", the instance itself is passed explicitly as the first argument, customarily named self:

class BankAccount:
    # Class Attribute (shared across all instances on the class object itself)
    bank_name = "Global Financial Trust"
    interest_rate = 0.04

    def __init__(self, account_holder: str, initial_balance: float = 0.0):
        # Instance Attributes (unique per object stored in self.__dict__)
        self.account_holder = account_holder
        self.balance = initial_balance

    def deposit(self, amount: float) -> float:
        if amount <= 0:
            raise ValueError("Deposit amount must be positive.")
        self.balance += amount
        return self.balance

# When you call account.deposit(500), Python internally executes:
# BankAccount.deposit(account, 500)

2. Instance Methods vs @classmethod vs @staticmethod

Method TypeDecoratorFirst ParameterBest Use Case
Instance MethodNoneself (the instance)Accessing and modifying instance state (self.balance).
Class Method@classmethodcls (the class)Alternative factory constructors (e.g. from_json, from_csv).
Static Method@staticmethodNonePure utility functions logically grouped inside the class namespace.
class ServerNode:
    total_nodes = 0

    def __init__(self, hostname: str, ip_address: str):
        self.hostname = hostname
        self.ip_address = ip_address
        ServerNode.total_nodes += 1

    @classmethod
    def from_connection_string(cls, conn_str: str):
        """Alternative constructor parsing 'hostname:ip'."""
        host, ip = conn_str.split(":")
        return cls(host, ip)

    @staticmethod
    def is_valid_ipv4(ip: str) -> bool:
        """Utility helper independent of any instance or class state."""
        octets = ip.split(".")
        return len(octets) == 4 and all(o.isdigit() and 0 <= int(o) <= 255 for o in octets)

node = ServerNode.from_connection_string("node-east-1:192.168.1.10")
print("Valid IP?", ServerNode.is_valid_ipv4("192.168.1.10"))

3. Magic Dunder Methods: Making Classes Pythonic

"Dunder" (Double Underscore) methods allow your custom classes to intercept Python operators (like ==, +, len(), and string casting):

class Vector2D:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    # __repr__ is for developers (unambiguous, matches constructor syntax)
    def __repr__(self) -> str:
        return f"Vector2D(x={self.x}, y={self.y})"

    # __str__ is for human-readable end users
    def __str__(self) -> str:
        return f"({self.x}, {self.y})"

    # Overload addition operator (+)
    def __add__(self, other: "Vector2D") -> "Vector2D":
        return Vector2D(self.x + other.x, self.y + other.y)

    # Overload equality operator (==)
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector2D):
            return False
        return self.x == other.x and self.y == other.y

v1 = Vector2D(2, 3)
v2 = Vector2D(4, 5)
v3 = v1 + v2

print("v3 Representation:", repr(v3))  # Vector2D(x=6, y=8)
print("v3 Equals Vector2D(6, 8)?", v3 == Vector2D(6, 8))  # True

Explore Related Tools

Hand-picked utilities and calculators related to this tool.

Related Tools