Classes
Objects, dunders, dataclasses, properties.
Python Classes
A class is a blueprint for objects. Define the shape once, instantiate as many as you need.
The basics
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def display_name(self):
return self.name.title()
alice = User("alice", "alice@x.com")
alice.display_name() # "Alice"
__init__ is the constructor. self is the convention for the instance reference (it's a parameter, not a keyword — you could name it anything, but don't).
Class attributes vs instance attributes
class Counter:
total_created = 0 # class attribute — shared
def __init__(self):
self.value = 0 # instance attribute — per object
Counter.total_created += 1
Reading Counter.total_created on the class works. Reading it on an instance also works (Python checks the instance first, then the class). Writing to it on an instance creates a NEW instance attribute that shadows the class one — a subtle gotcha.
dunder methods
Methods named __like_this__ ("dunder" = double-underscore) hook into Python syntax:
class Money:
def __init__(self, cents):
self.cents = cents
def __repr__(self):
return f"Money({self.cents})"
def __add__(self, other):
return Money(self.cents + other.cents)
def __eq__(self, other):
return isinstance(other, Money) and self.cents == other.cents
def __hash__(self):
return hash(self.cents)
a = Money(100) + Money(200) # __add__
print(a) # Money(300) — __repr__
{Money(100): "x"} # __hash__ + __eq__
The big ones: __init__, __repr__, __eq__, __hash__, __lt__, __add__, __len__, __iter__, __contains__, __call__.
Inheritance
class Animal:
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "Woof"
class Puppy(Dog):
def speak(self):
return super().speak() + "!"
Puppy().speak() # "Woof!"
super() calls the parent's version. Multiple inheritance works too (class C(A, B):); Python uses C3 linearisation for method resolution order. For most apps, prefer composition.
dataclasses
@dataclass (3.7+) writes __init__, __repr__, and __eq__ for you:
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
age: int = 0
Equivalent to writing __init__, __repr__, __eq__ by hand. There's also frozen=True to make instances immutable, and kw_only=True to force keyword-only args.
Properties
A @property makes a method look like an attribute:
class Circle:
def __init__(self, r):
self.r = r
@property
def area(self):
return 3.14159 * self.r ** 2
c = Circle(5)
c.area # 78.539... (no parens)
Pair with a setter for write access, or omit it for read-only.
Class methods + static methods
class User:
@classmethod
def from_row(cls, row): # alternate constructor
return cls(name=row[0], email=row[1])
@staticmethod
def is_email(s): # not tied to an instance OR class
return "@" in s
Naming conventions
name— public attribute._name— private by convention (Python doesn't enforce it).__name— name-mangled (_ClassName__name); use sparingly.__name__— dunder; reserved by Python.
When NOT to use a class
If you have a few related fields and one or two functions over them, a @dataclass is fine — but a plain dict or namedtuple is often enough. Python isn't Java; you don't need a class for everything.