Python Classes and Objects
A complete beginner-friendly guide to Object-Oriented Programming in Python — covering class definitions, the __init__ constructor, the self keyword, instance vs class attributes, methods, and real-world design patterns for 2026.
Last Updated
March 2026
Read Time
26 min
Level
Beginner
What are Classes and Objects in Python?
A class in Python is a blueprint or template for creating objects. It defines a set of attributes (data) and methods (behaviour) that describe what an object of that type looks like and what it can do. An object is a concrete instance of a class — the actual thing built from the blueprint, occupying real memory and holding its own values.
Think of a class as an architect's drawing for a house, and each object as an actual house built from that drawing. The drawing specifies that every house has a number of rooms, a roof type, and a front door — but each individual house built from it can have different values for those properties. In the same way, a Python class named Car might define attributes like brand, color, and speed, while every car object created from that class carries its own specific values.
Python is a multi-paradigm language, and classes are the foundation of its Object-Oriented Programming (OOP) paradigm. Everything in Python — integers, strings, lists, functions, even classes themselves — is actually an object built from some class. This is why Python is often described as being "object-oriented to its core": even x = 5 secretly creates an object of the built-in int class.
OOP in Python is organised around four pillars: Encapsulation (bundling data and behaviour together), Abstraction (hiding internal complexity behind a simple interface), Inheritance (reusing and extending existing classes), and Polymorphism (treating different object types through a common interface). This article focuses on the foundation of all four — how to define classes and create objects from them.
How Classes and Objects Evolved in Python
Object-oriented design was part of Python's DNA from the very beginning. Guido van Rossum built Python's class system partly on ideas borrowed from Modula-3 and C++, while deliberately keeping the syntax lighter and less ceremonial than either. Over more than three decades, the way Python handles classes has been refined significantly. Here is a timeline of the milestones that shaped modern Python OOP:
- ▶
1991 — Python 0.9.0 already included classes with multiple inheritance and exception classes, making OOP a first-class citizen from Python's very first public release.
- ▶
1997 — Iterators and richer special methods began appearing, letting user-defined classes plug into for-loops and built-in functions like len() and str().
- ▶
2001-2002 — Python 2.2 unified 'classic classes' and 'new-style classes,' making every class inherit from object and giving all classes consistent, predictable behaviour.
- ▶
2008 — Python 3.0 removed the old classic-class system entirely. Every class in Python 3 automatically inherits from the built-in object class.
- ▶
2015 — PEP 484 introduced type hints, letting developers annotate class attributes and method signatures for better tooling and readability without sacrificing dynamic typing.
- ▶
2018 — Python 3.7 introduced the @dataclass decorator (PEP 557), dramatically reducing the boilerplate needed to write simple data-holding classes.
- ▶
2021 — Python 3.10 added structural pattern matching, which works elegantly with classes through the __match_args__ special attribute.
- ▶
2022-2026 — Python 3.11 through 3.14 focused on performance: faster attribute lookup, faster method calls, and continued refinement of typing features like Self and dataclass slots, making class-based code faster than ever without changing how it's written.
Core Concepts Every Python Class Uses
Before writing a single class, it helps to know the vocabulary. These are the building blocks that appear in almost every Python class you will ever write:
Every class definition starts with the class keyword followed by a name written in PascalCase, e.g. class Car:. This tells Python you are defining a new type, not just a function.
A special method automatically called when a new object is created. It initialises the object's starting attributes, e.g. setting brand, model, and speed the moment a Car object is built.
The first parameter of every instance method, representing the specific object the method is being called on. self.speed refers to that particular object's speed, not any other object's.
Data that belongs to one specific object, usually set inside __init__ using self.attribute = value. Each object keeps its own independent copy.
Data shared by every object of a class, defined directly inside the class body outside any method. Useful for constants or counters shared across all instances.
Regular functions defined inside a class that operate on a specific object via self, e.g. def accelerate(self): changes that object's own speed.
Methods decorated with @classmethod that receive the class itself (cls) instead of an instance. Often used as alternative constructors.
Methods decorated with @staticmethod that take neither self nor cls. They live inside a class purely for organisational reasons, behaving like plain functions.
The act of creating an object from a class by calling the class name like a function, e.g. my_car = Car('Tesla', 'Model 3'). Python calls __init__ automatically behind the scenes.
Bundling data and the methods that operate on it inside one unit. Python signals privacy by convention using a single underscore _attr or double underscore __attr, rather than strict enforcement.
A class can inherit attributes and methods from another class using class Child(Parent):, promoting code reuse and building specialised versions of general classes.
Different classes can implement the same method name in their own way, letting code written for a general type work seamlessly with any of its subclasses.
Special double-underscore methods like __str__, __eq__, and __len__ let your custom objects work naturally with print(), comparisons, and built-in functions.
How Object Creation Works — Flowchart
Understanding exactly what happens when you write my_car = Car("Tesla", "Model 3") demystifies a lot of confusion beginners have about classes. The diagram below traces the precise sequence of steps Python follows, from reading the class definition to handing you back a fully initialised object.
Code Execution Flow — from source to output
Key insight: __new__() and __init__() are two separate steps. __new__() actually creates the empty object in memory, while __init__() only initialises the attributes on an object that already exists. Beginners rarely need to override __new__(), but knowing it exists explains why __init__() never needs to "return" the object — it is already there by the time __init__() runs.
How Python Classes Work — self, __init__, and Attributes Explained
Three ideas trip up nearly every Python beginner when they first meet classes: the self keyword, the __init__ constructor, and the difference between instance attributes and class attributes. Getting comfortable with these three concepts is 90% of mastering basic Python OOP.
🙋 The self Keyword — Why Every Method Needs It
Every instance method in a Python class takes self as its first parameter. This is not a special keyword enforced by the language — it is simply a strong convention, and Python automatically passes the object itself into that first parameter whenever you call a method on it. When you write my_car.accelerate(), Python actually runs it behind the scenes as Car.accelerate(my_car). The name self is just a convention; you could technically call it anything, but doing so would confuse every other Python developer who reads your code.
The purpose of self is to let a method know which specific object it is operating on. Without it, a method would have no way to distinguish between two different Car objects — self.speed for one car would be indistinguishable from self.speed for another. This is fundamentally different from languages like Java, where this is implicit; Python makes the object reference explicit and visible in every method signature.
🔧 The __init__ Constructor
__init__ is a special "dunder" (double-underscore) method that Python automatically calls immediately after a new object is created. Its job is to set up the object's starting state — assigning initial values to instance attributes. Despite being called a "constructor" in everyday conversation, __init__ technically initialises an object rather than constructing it; the true construction happens in the lesser-known __new__ method, which most beginners never need to touch.
A typical __init__ signature looks like def __init__(self, brand, model):. Every parameter after self becomes a value you must supply when creating the object, e.g. Car("Tesla", "Model 3"). Inside the method body, assignments like self.brand = brand attach that value permanently to the specific object being built.
📦 Instance Attributes vs 🌐 Class Attributes
Instance attributes are defined inside methods (almost always __init__) using self.attribute_name = value. Each object gets its own separate copy, stored on the object itself. Changing one object's instance attribute never affects any other object.
Class attributes, by contrast, are defined directly in the class body, outside of any method. They are shared across every object of that class, stored once on the class itself rather than duplicated per object. They are ideal for constants (like a tax rate) or shared counters (like tracking how many objects have been created). A common beginner mistake is reassigning a class attribute through an instance — writing my_car.wheels = 6 creates a brand-new instance attribute that shadows the class attribute for that object only, rather than changing it for every car.
⚙️ Methods — Instance, Class, and Static
Python offers three kinds of methods inside a class. Instance methods take self and can read or modify that specific object's data. Class methods, marked with @classmethod, take cls instead of self and operate on the class itself — they are commonly used to build alternative constructors, such as Car.from_string("Tesla-Model3"). Static methods, marked with @staticmethod, take neither self nor cls; they behave like ordinary functions that happen to be logically grouped inside the class for organisational clarity.
Simple rule to remember: __init__ sets up a new object's data. self lets a method know which object it is working with. Instance attributes belong to one object; class attributes belong to all of them.
Instance Attributes vs Class Attributes — Key Differences
This is one of the most frequently confused topics for Python beginners. The comparison table below lays out the differences clearly, side by side.
Python OOP vs Other Languages — Comparison
Python's approach to classes is deliberately lighter than many other object-oriented languages. This table compares how core OOP concepts are expressed across popular languages.
Advantages and Disadvantages of Using Classes in Python
Classes are powerful, but they are not always the right tool. Understanding both the benefits and the trade-offs helps you decide when object-oriented design genuinely improves your code and when a simple function or dictionary is enough.
Anatomy of a Python Class — Architecture Diagram
The diagram below breaks a Python class down into its structural layers — from the outward-facing class name down to the memory each object occupies at runtime. Seeing the pieces laid out together makes it much easier to understand how a class definition becomes a living, working object.
Your First Python Class — A Complete Example
The best way to understand classes is to see one from start to finish. Below is a complete, realistic Car class demonstrating a constructor, instance attributes, a class attribute, and two instance methods.
class Car:
# Class attribute — shared by every Car object
wheels = 4
def __init__(self, brand, model, speed=0):
# Instance attributes — unique to each object
self.brand = brand
self.model = model
self.speed = speed
def accelerate(self, amount):
self.speed += amount
print(f"{self.brand} {self.model} is now going {self.speed} km/h")
def brake(self, amount):
self.speed = max(0, self.speed - amount)
print(f"{self.brand} {self.model} slowed to {self.speed} km/h")
car1 = Car("Tesla", "Model 3")
car2 = Car("Toyota", "Corolla")
car1.accelerate(60)
car2.accelerate(30)
car1.brake(10)
print(f"{car1.brand} has {car1.wheels} wheels")Output
Tesla Model 3 is now going 60 km/h Toyota Corolla is now going 30 km/h Tesla Model 3 slowed to 50 km/h Tesla has 4 wheelsPractice This Code — Live Editor
Line-by-Line Explanation
- ▶
class Car:— Declares a new class named Car. By convention, class names use PascalCase (each word capitalised, no underscores). - ▶
wheels = 4— A class attribute defined directly in the class body. Every Car object shares this same value unless individually overridden. - ▶
def __init__(self, brand, model, speed=0):— The constructor. speed=0 gives it a default value, so callers can omit it when creating a car with zero starting speed. - ▶
self.brand = brand— Takes the brand argument passed in and stores it as an instance attribute on this specific object. - ▶
def accelerate(self, amount):— An instance method. self lets it read and modify this object's own speed attribute, not any other car's. - ▶
car1 = Car("Tesla", "Model 3")— Instantiation. Python automatically calls __init__ behind the scenes, passing car1 itself in as self. - ▶
car1.accelerate(60)— Calling a method on an object. Python translates this internally to Car.accelerate(car1, 60).
Modern Python Classes — @dataclass and Type Hints
Since Python 3.7, the @dataclass decorator has become the standard way to write simple, data-holding classes without repetitive boilerplate. It automatically generates __init__, __repr__, and __eq__ based purely on type-annotated class attributes.
from dataclasses import dataclass
@dataclass
class Car:
brand: str
model: str
speed: int = 0
car1 = Car("Tesla", "Model 3")
print(car1)Output
Car(brand='Tesla', model='Model 3', speed=0)Notice there is no manual __init__ and no manual __repr__ — the decorator writes them for you based on the annotated attributes. This is now the preferred style for classes whose main purpose is holding structured data, while classes with significant custom behaviour typically still use the traditional __init__ style shown earlier.
Where Classes and Objects Are Used — Real-World Applications
Object-oriented design is not an academic exercise — it is the backbone of nearly every substantial Python application in production today. Here is where classes show up across the ecosystem:
- ▶
🌐 Django Models — Every database table in Django is represented as a Python class inheriting from models.Model. Each row in the table becomes an object, with class attributes mapping directly to database columns.
- ▶
🧠 Machine Learning Models — scikit-learn, TensorFlow, and PyTorch all expose their models as classes. A neural network defined in PyTorch is a class inheriting from nn.Module, with layers as instance attributes and forward() as an instance method.
- ▶
🌍 Flask & FastAPI Applications — Request handlers, database schemas (via Pydantic or SQLAlchemy models), and dependency-injected services are almost always expressed as classes in production-grade web applications.
- ▶
🎮 Game Development — Pygame projects typically define a class for each game entity — Player, Enemy, Bullet — each holding its own position, health, and update() method, making complex games manageable.
- ▶
🏦 Financial Systems — A BankAccount class encapsulates balance, account number, and methods like deposit() and withdraw(), ensuring balance can only ever change through controlled, validated methods.
- ▶
🤖 GUI Applications — Tkinter, PyQt, and Kivy applications are built almost entirely from classes representing windows, buttons, and custom widgets, each managing its own visual state.
- ▶
☁️ Cloud SDKs & APIs — The boto3 (AWS) and google-cloud-python SDKs expose services as client classes, with methods representing each API operation you can perform.
- ▶
🔬 Scientific Simulations — Physics engines and simulation frameworks represent particles, bodies, or agents as objects, each tracking its own position, velocity, and mass across simulation steps.
Why Should You Master Classes and Objects in 2026?
Functions alone can take you far in Python, but almost every serious Python codebase you will encounter professionally — from Django to PyTorch — is organised around classes. Here's why investing time in OOP pays off:
- ▶
📚 Required to Read Framework Source Code — Django, FastAPI, and virtually every major library expose their functionality through classes. Without understanding OOP, reading their documentation and source code becomes far harder.
- ▶
🧩 Essential for Larger Projects — As soon as a project grows past a handful of files, classes provide the structure needed to keep related data and logic organised and testable.
- ▶
💼 Expected in Technical Interviews — OOP design questions — 'design a parking lot system,' 'design a library management system' — are standard in Python developer interviews at every experience level.
- ▶
🤝 Team Collaboration — Classes create clear contracts between different parts of a codebase, making it easier for multiple developers to work on the same project without constantly breaking each other's code.
- ▶
🧠 Deepens Understanding of Python Itself — Once you understand classes, concepts like Python's built-in types, exceptions, and even functions themselves become clearer — because they are all objects too.
- ▶
🚀 Foundation for Design Patterns — Classic software design patterns (Factory, Singleton, Observer, Strategy) are all expressed through classes, and understanding them unlocks cleaner, more maintainable code.
Python Versions and Their Impact on Classes
Python's class system has steadily gained new capabilities over the years. Knowing which version introduced which feature helps you understand why older tutorials sometimes look different from modern code:
- ▶
Python 2.2 — New-Style Classes — Unified classic and new-style classes so every class consistently inherits from object, fixing long-standing inconsistencies in attribute lookup.
- ▶
Python 3.0 — Object Inheritance by Default — Removed the old classic-class system entirely; writing class Car: is now automatically equivalent to class Car(object):.
- ▶
Python 3.6 — Variable Annotations — Allowed attribute type hints directly in the class body (brand: str), paving the way for dataclasses a year later.
- ▶
Python 3.7 — @dataclass Decorator — Auto-generates __init__, __repr__, and __eq__ for simple data classes, cutting boilerplate dramatically.
- ▶
Python 3.10 — Structural Pattern Matching — match/case statements can destructure class instances directly using __match_args__, enabling elegant object-based branching.
- ▶
Python 3.11 — Self Type Hint — Added typing.Self, letting methods that return an instance of their own class (like builder patterns) be type-hinted accurately.
- ▶
Python 3.13-3.14 — Performance Gains — Faster attribute lookup and method calls under the hood mean class-based code runs measurably faster without any change to how you write it.
Python Classes & Objects Interview Questions — Beginner Level
These are the most commonly asked interview questions about Python classes and objects for freshers and beginner-level positions. Master these before any Python OOP interview.
Practice Questions — Test Your Understanding
Test your grasp of Python classes and objects with these practice questions. Try to answer each one before revealing the answer — active recall builds much stronger retention than passive reading.
1. What will happen if you forget to include self as the first parameter in an instance method?
Easy2. If a class attribute is a mutable object like a list, what danger should you watch out for?
Medium3. What does Car.__mro__ return, and why does it matter?
Hard4. What is the difference between __repr__ and __str__?
Medium5. Why does modifying an object through a method sometimes not show updated changes elsewhere in your code?
Hard6. What is the purpose of the @property decorator in a class?
Medium7. What happens if a subclass does not define its own __init__ method?
Easy8. Why is composition ('has-a' relationship) sometimes preferred over inheritance ('is-a' relationship)?
HardConclusion — Why Classes and Objects Matter
Classes and objects are not just an advanced topic to learn eventually — they are the organisational backbone of virtually every substantial Python project you will encounter, from Django's models to PyTorch's neural networks to the everyday scripts powering automation pipelines across the industry.
The core ideas covered here — a class as a blueprint, an object as its concrete instance, __init__ for setup, self for identifying which object a method is working on, and the distinction between instance and class attributes — form the foundation everything else in Python OOP builds upon, including inheritance, polymorphism, and design patterns.
The natural next step after mastering classes and objects is Inheritance and Polymorphism — learning how to build specialised classes on top of general ones, and how to write code that works seamlessly across an entire family of related classes. From there, encapsulation techniques, dunder methods, and design patterns build naturally on the foundation you've established here.
Classes turn scattered data and logic into coherent, reusable building blocks. Whether you are building a small game, a production web API, or training a neural network, the discipline of organising your code around well-designed classes pays dividends as your projects grow. Practice writing your own classes today — start small, and the concepts will click quickly. 🧱