A class is a named template that defines the attributes and methods shared by all objects of that type, serving as a blueprint from which individual instances are created, each with its own copy of the class's instance attributes.
Blueprints for objects
What you'll learn: What a class is (a blueprint), what an instance is (an object built from that blueprint), constructors, and the difference between instance and class attributes.
How to read this tab: This concept page explains the why. The Python-specific syntax is at Object-Oriented Python.
A class is a blueprint — it describes what data an object will hold and what actions it can perform, and from it you create as many individual objects as you need.
The blueprint/building analogy: a class is the architectural drawing; objects are the actual buildings. The drawing says "buildings have 3 bedrooms and a kitchen." Each building is built from the same drawing but can have different colours, owners, and furnishings. In code: the class defines the structure; each instance has its own data.
Class as blueprint, object as instance
The class BankAccount defines what every account has (owner, balance) and what it can do (deposit, withdraw). Each actual account — Priya's, Rahul's — is a separate instance with its own data but sharing the class's methods.
class BankAccount:
def __init__(self, owner, balance=0): # constructor
self.owner = owner # instance attribute
self.balance = balance
def deposit(self, amount): # instance method
self.balance += amount
return self.balance
def __str__(self): # string representation
return f"Account({self.owner}: Rs{self.balance:,.2f})"
priya = BankAccount("Priya", 10000)
rahul = BankAccount("Rahul")
priya.deposit(500)
print(priya) # Account(Priya: Rs10,500.00)The constructor runs once, when the object is created. In Python it's __init__(self, ...). In Java it's a method with the same name as the class. Its job: set up the object's initial state by assigning instance attributes. Keep constructors simple — do assignment, not complex computation. Move complex setup into separate methods.
The constructor
The constructor runs automatically when an instance is created. Python: __init__. Java/C++: method with the class name. Rust: associated function new(). Its job is to initialise the instance's attributes to valid starting values.
Three types of method in Python: Instance methods (take self — access instance data). Class methods (@classmethod, take cls — access class data, useful for alternative constructors). Static methods (@staticmethod — take neither — just a regular function namespaced inside the class). Use instance methods by default; the others only when there's a clear reason.
Instance, class, and static methods
An instance method operates on one specific object (self in Python is that object). A class method (@classmethod) operates on the class itself — cls is the class. A static method (@staticmethod) is just a function grouped inside a class for organisation — it receives no implicit first argument.
Python's access control is convention, not enforcement. One underscore: _method — "private by convention, don't use this from outside." Two underscores: __method — name mangling makes it harder to access from outside (becomes _ClassName__method) but not impossible. No underscore: fully public. Java enforces private with the compiler; Python trusts the developer.
Access control
Java: private, protected, public — enforced by compiler. Python: _name = convention ("don't use externally"), __name = name mangling (harder to access but not impossible). Rust: private by default; pub opts in to visibility.
✅ Beginner tab complete — check your understanding
- I can explain class vs instance in plain English without code
- I know what a constructor is and what it does (initialises the object's attributes)
- I know the difference between an instance attribute (belongs to one object) and a class attribute (shared by all instances)
Access control, dunder methods, and dataclasses
What you'll learn: Access control (public/private/protected) and how Python's convention-based approach differs from Java's enforced access. Dunder methods for operator overloading. Dataclasses as the modern shorthand.
How to read this tab: Python has no enforced private — _ is a convention, __ triggers name mangling but isn't truly private. Understanding this makes you read Python code correctly.
Class attributes are shared — this surprises everyone once. If a class attribute is mutable (a list or dict), all instances share the same object. Mutating it in one instance affects all instances. Best practice: only use class attributes for constants and counters that should truly be shared. Put data that belongs to each instance in __init__ as instance attributes.
Class attributes vs. instance attributes
Class attributes are shared across all instances. Instance attributes are private to one object. A class attribute can be read through an instance, but assigning to it through an instance creates a new instance attribute that shadows the class attribute.
class Dog:
species = "Canis familiaris" # class attribute — shared
def __init__(self, name):
self.name = name # instance attribute — unique
rex = Dog("Rex")
print(rex.species) # "Canis familiaris" — reads class attr
rex.species = "Wolf" # creates INSTANCE attribute on rex
print(rex.species) # "Wolf" — instance shadows class
print(Dog.species) # "Canis familiaris" — class unchangedDunder methods hook your class into Python's built-in operations. __str__ is called by print() and str(). __len__ is called by len(). __eq__ is called by ==. __lt__ is called by <. __iter__ and __next__ make your object iterable. Implementing these makes custom objects feel native — they work with all Python's built-in functions and idioms.
Dunder (magic) methods
Python classes support operator overloading and protocol integration via double-underscore methods: __str__ (str()), __len__ (len()), __add__ (+), __eq__ (==), __iter__/__next__ (for loops), __enter__/__exit__ (with statements).
@dataclass is the modern way to write data-holding classes. It auto-generates __init__, __repr__, and __eq__ from your field annotations. @dataclass class Point: x: float; y: float gives you a fully functional class in 3 lines. With frozen=True it's immutable and hashable. With order=True it automatically generates comparison operators.
Dataclasses
Python 3.7 @dataclass (PEP 557) auto-generates __init__, __repr__, __eq__ from annotated fields. Kotlin data class. Java 16 record. All eliminate boilerplate for data-holding classes.
self is the Python-wide convention, not a keyword.✅ Intermediate tab complete — check your understanding
- I know Python doesn't enforce private access — _ is a convention meaning "please don't use this directly"
- I know __ (double underscore) triggers name mangling, not true private access
- I can write a @dataclass to avoid boilerplate __init__, __repr__, __eq__
Metaclasses, MRO, and class creation
What you'll learn: Metaclasses — the classes whose instances are classes. The Method Resolution Order (MRO) — how Python resolves method calls in multiple inheritance. The full class creation process.
How to read this tab: Read after Stage 2 when you encounter frameworks like Django's ORM or Python's ABCs and want to understand how they work internally.
Metaclasses and class creation
In Python, a class is itself an object — an instance of its metaclass, type by default. Executing a class statement calls type(name, bases, namespace). Custom metaclasses (subclasses of type) intercept class creation — used by Django's ORM to translate class-level field definitions into database schema metadata.
Method resolution order (MRO)
Python uses C3 linearisation (Barrett et al., 1996) to compute the MRO — the order in which base classes are searched during method lookup. Accessible via C.__mro__. Guarantees: a class precedes its parents; left-to-right order among parents is preserved; local precedence rule holds.
Python Language Reference §3.3 — Special method names. PEP 557 — Data Classes. Barrett et al. (1996) C3 linearisation. OOPSLA '96. Java Language Specification §8 — Class Declarations.
✅ Expert tab complete
- I know that type is the default metaclass — type is what creates classes
- I can use type.mro() to inspect the method resolution order of any class