🧱 Python OOP

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:

🏗️
class keyword

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.

🔧
__init__ Constructor

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.

🙋
self Keyword

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.

📦
Instance Attributes

Data that belongs to one specific object, usually set inside __init__ using self.attribute = value. Each object keeps its own independent copy.

🌐
Class Attributes

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.

⚙️
Instance Methods

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.

🏛️
Class Methods

Methods decorated with @classmethod that receive the class itself (cls) instead of an instance. Often used as alternative constructors.

🧊
Static Methods

Methods decorated with @staticmethod that take neither self nor cls. They live inside a class purely for organisational reasons, behaving like plain functions.

🏭
Instantiation

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.

🔒
Encapsulation

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.

🧬
Inheritance

A class can inherit attributes and methods from another class using class Child(Parent):, promoting code reuse and building specialised versions of general classes.

🎭
Polymorphism

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.

Dunder Methods

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.

📝 Define the Classclass Car: ...
instantiate
🏭 Call the ClassCar("Tesla", "Model 3")
Python calls
🧱 __new__() RunsAllocates raw object in memory
then calls
🔧 __init__() RunsSets self.brand, self.model
assigns via self
📦 Instance Attributes SetObject now holds its own data
construction complete
🔗 Reference Returnedmy_car points to the new object
usable in code
🖨️ Object Ready to Usemy_car.brand → "Tesla"

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.

FeatureInstance AttributeClass Attribute
Defined whereInside a method, usually __init__Directly inside class body
Belongs toOne specific objectThe class, shared by all objects
Syntaxself.name = valuename = value (no self)
MemorySeparate copy per objectOne shared copy
Accessobj.attributeClassName.attribute or obj.attribute
Typical useData that differs per objectConstants, counters, defaults
Changing it via instanceUpdates only that objectCreates a new instance attribute (shadows class attribute)
Exampleself.brand = "Tesla"wheels = 4

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.

FeaturePythonJavaC++JavaScript
Class keywordclassclassclassclass (ES6+)
Constructor name__init__Same as class nameSame as class nameconstructor()
Self-referenceself (explicit parameter)this (implicit)this (implicit)this (implicit)
Access modifiersConvention only (_x, __x)public / private / protectedpublic / private / protected# for true private fields
Multiple inheritance✅ Supported❌ Interfaces only✅ Supported❌ Not supported
Type declarationsOptional (type hints)MandatoryMandatoryOptional (TypeScript)
Everything is an object✅ Yes, always❌ Primitives excluded❌ Primitives excluded❌ Primitives excluded
Boilerplate for simple classesMinimal (or @dataclass)High (getters/setters)HighMedium

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.

✅ Advantages
Models Real-World Entities NaturallyClasses let you represent things like Car, User, or BankAccount as coherent units of data and behaviour, matching how people naturally think about problems.
Encourages Code Reuse via InheritanceA general Vehicle class can be extended into Car, Truck, and Motorcycle without duplicating shared logic, saving significant development time.
Encapsulation Reduces BugsBundling data with the methods that operate on it prevents scattered, inconsistent logic from mutating the same data in incompatible ways.
Polymorphism Simplifies CodeCode written against a general interface automatically works with any subclass, reducing the need for long if/elif chains checking object types.
Great for Large, Long-Lived CodebasesAs a codebase grows past a few hundred lines, classes provide organisational structure that a flat collection of functions struggles to match.
Works Seamlessly with FrameworksDjango models, Flask views, and most major Python frameworks are built around classes, making OOP knowledge essential for real-world Python work.
Dataclasses Minimise BoilerplateThe @dataclass decorator auto-generates __init__, __repr__, and __eq__, making simple data-holding classes almost as concise as plain dictionaries.
❌ Disadvantages
Overkill for Small ScriptsA ten-line automation script rarely benefits from a class hierarchy; plain functions are simpler, faster to write, and easier to read.
Weak Privacy EnforcementPython's underscore-based privacy conventions are not enforced by the language, so nothing truly stops external code from accessing 'private' attributes.
Can Encourage Over-EngineeringBeginners sometimes build deep inheritance hierarchies for problems that a single function or a dictionary would solve more simply.
Slightly Slower Attribute AccessLooking up an attribute through self involves more overhead than accessing a local variable, which matters only in very performance-sensitive code.
Steeper Initial Learning CurveConcepts like self, __init__, inheritance, and dunder methods take longer for absolute beginners to internalise than simple function-based code.
Multiple Inheritance ComplexityPython's support for multiple inheritance is powerful but can create confusing method resolution order (MRO) issues in deeply layered class hierarchies.

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.

Class Declaration
class Car:DocstringOptional base class(es) in parentheses
Class Body — Shared Layer
Class attributes (wheels = 4)Class methods (@classmethod)Static methods (@staticmethod)
Constructor Layer
__init__(self, ...)Parameter validationself.attribute = value assignments
Instance Methods Layer
def accelerate(self)def brake(self)Dunder methods (__str__, __eq__)
Object Instances (Runtime)
car1 → {brand: 'Tesla', speed: 0}car2 → {brand: 'Toyota', speed: 20}Each holds its own __dict__
Memory (CPython)
Heap-allocated PyObjectReference countType pointer back to Car class

Architecture Diagram

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.

🐍 Pythoncar.py
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 wheels

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

🐍 Pythoncar_dataclass.py
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.

VersionReleasedOOP Feature Added
Python 2.22001New-style classes unify object model
Python 3.02008Every class inherits from object automatically
Python 3.62016Class attribute type annotations
Python 3.72018@dataclass decorator
Python 3.102021Pattern matching on class instances
Python 3.112022typing.Self for accurate method typing
Python 3.132024Faster attribute/method access performance

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?

Easy

2. If a class attribute is a mutable object like a list, what danger should you watch out for?

Medium

3. What does Car.__mro__ return, and why does it matter?

Hard

4. What is the difference between __repr__ and __str__?

Medium

5. Why does modifying an object through a method sometimes not show updated changes elsewhere in your code?

Hard

6. What is the purpose of the @property decorator in a class?

Medium

7. What happens if a subclass does not define its own __init__ method?

Easy

8. Why is composition ('has-a' relationship) sometimes preferred over inheritance ('is-a' relationship)?

Hard

Conclusion — 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.

Your GoalShould You Learn Classes & Objects?
Reading Django / FastAPI source code✅ Absolutely — both are built entirely on classes
Building machine learning models✅ Yes — PyTorch and TensorFlow models are classes
Writing a quick one-off automation script⚠️ Optional — plain functions are often simpler
Preparing for developer interviews✅ Yes — OOP design questions are extremely common
Working on a large, multi-file codebase✅ Yes — classes provide essential structure
Learning data science / pandas scripting⚠️ Useful, but pandas itself hides most class details
Building a game with Pygame✅ Yes — entities are almost always modelled as classes
Understanding Python itself more deeply✅ Yes — even built-in types are implemented as classes

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

Frequently Asked Questions (FAQ)