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__
__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.