Python Story

Chapter #5 – Classes

anatomy, __init__, properties, dunder methods, dataclasses

5.0 Prologue

A class is the primary way Python bundles data (attributes) and behavior (methods). This chapter walks through every member kind a class can contain, from the initializer to special dunder methods to the modern @dataclass decorator.

5.1 Class Anatomy

A class body may contain:
  • Class variables — shared by all instances
  • Instance variables — per-instance state set in __init__
  • Instance methods — receive self as the first parameter
  • Class methods — decorated with @classmethod; receive cls
  • Static methods — decorated with @staticmethod; no implicit first parameter
  • Properties — managed attribute access via @property
  • Dunder methods — special names like __repr__, __len__
class BankAccount: interest_rate = 0.03 # class variable def __init__(self, owner: str, balance: float = 0.0) -> None: self.owner = owner self._balance = balance # _ prefix = private by convention @property def balance(self) -> float: return self._balance def deposit(self, amount: float) -> None: if amount <= 0: raise ValueError("amount must be positive") self._balance += amount def withdraw(self, amount: float) -> bool: if amount > self._balance: return False self._balance -= amount return True @classmethod def from_dict(cls, data: dict) -> "BankAccount": return cls(data["owner"], data.get("balance", 0.0)) @staticmethod def validate_amount(amount: float) -> bool: return amount > 0 def __repr__(self) -> str: return f"BankAccount({self.owner!r}, {self._balance:.2f})"

5.2 __init__ and Instance Variables

__init__ is the initializer, called immediately after a new instance is created. It sets up instance variables by assigning to self.name. Python has no access modifiers; a single underscore prefix (_attr) signals "internal use" and a double underscore (__attr) triggers name-mangling. __new__ is the allocator (rarely overridden). The common pattern is __init__ only.

5.3 Properties

The @property decorator turns a method into a read-only attribute. Adding @name.setter enables controlled writes: class Temperature: def __init__(self, celsius: float) -> None: self._celsius = celsius @property def celsius(self) -> float: return self._celsius @celsius.setter def celsius(self, value: float) -> None: if value < -273.15: raise ValueError("below absolute zero") self._celsius = value @property def fahrenheit(self) -> float: return self._celsius * 9 / 5 + 32

5.4 Dunder Methods

Dunder (double-underscore) methods let user-defined classes integrate with Python's built-in syntax and functions:
Method Triggered by
__repr__repr(obj), interactive display
__str__str(obj), print(obj)
__len__len(obj)
__contains__item in obj
__getitem__obj[key]
__iter__for x in obj
__eq__, __lt__, ...comparison operators
__add__, __mul__, ...arithmetic operators
__enter__, __exit__with obj as x
__call__obj()

5.5 Dataclasses

The @dataclass decorator (Python 3.7+) auto-generates __init__, __repr__, and __eq__ from annotated fields: from dataclasses import dataclass, field @dataclass(order=True, frozen=True) class Point: x: float y: float label: str = "" tags: list[str] = field(default_factory=list) def distance(self) -> float: return (self.x**2 + self.y**2) ** 0.5 p1 = Point(3.0, 4.0, "origin") p2 = Point(0.0, 0.0) print(p1 > p2) # True (order=True compares fields in order) frozen=True makes instances immutable (and hashable). order=True generates comparison methods. Use field(default_factory=...) for mutable defaults.

5.6 __slots__

By default, every instance stores attributes in a __dict__. Declaring __slots__ replaces that dict with a fixed set of slots, reducing memory and speeding up attribute access: class Pixel: __slots__ = ("x", "y", "color") def __init__(self, x: int, y: int, color: str) -> None: self.x = x self.y = y self.color = color Slotted classes cannot have arbitrary extra attributes and do not inherit a __dict__ unless explicitly added.

5.7 Epilogue

This chapter detailed the anatomy of Python classes: instance and class variables, properties, dunder methods, dataclasses, and slots. The next chapter examines how classes relate to one another through inheritance and composition.

5.8 References

Data model (dunder methods) — python.org
dataclasses module — python.org
Python classes — Real Python
__slots__ — python.org