Python OOP: Classes, Objects & Constructors Masterclass

What is OOP in Python?
Object-Oriented Programming (OOP) in Python is a programming paradigm where you model real-world entities as objects — combining data (attributes) and behaviour (methods) into reusable class blueprints. Python's class keyword and __init__ constructor make it one of the most readable OOP languages available.
Introduction to OOP
Welcome to the foundation of modern software development: Object-Oriented Programming (OOP). While procedural programming focuses on a sequence of steps or functions, OOP focuses on Objects—entities that combine data (attributes) and behavior (methods).
In this tutorial, we will cover:
- The difference between a Class and an Instance.
- How to define your first Class in Python.
- The power of Constructors (
__init__).
What is a Class?
Think of a Class as a blueprint, template, or a prototype. It doesn't "exist" as a piece of data itself, but it defines what its objects will look like and how they will behave.
Example: The Circle Blueprint
Imagine a Circle class. Every circle in the world has:
- Attributes: Radius, Color, Diameter.
- Methods: Calculate Area, Calculate Perimeter.
The class defines these properties, but it doesn't represent a specific circle until you create an instance.
What is an Instance (Object)?
An Object is a specific "thing" created from the blueprint. If Circle is the blueprint, then a "Red Circle with a 5cm radius" is the Instance.
Every instance lives in its own place in memory and can have its own unique data while sharing the same structure defined by the class.
Creating a Class in Python
In Python, we use the class keyword. By convention, class names use UpperCamelCase.
The Need for Constructors
If we create an empty class, we have to manually assign attributes to every object we create:
This is tedious and error-prone. This is where the Constructor comes in.
The Constructor (__init__)
The __init__ method is a special "dunder" method that runs automatically whenever you create a new object. It's used to initialize the object's attributes.
Parameterized Constructor
This is the most common type, where you pass values to the class when creating an object.
Understanding 'self'
The 'self' parameter is a reference to the current instance of the class. It allows you to access variables that belong to the class from within its methods.
Python Trick: Multiple Constructors?
A common question is: Can I have multiple __init__ methods in one class?
In Python, the answer is No. If you define multiple __init__ methods, Python will only use the last one defined.
Python doesn't support "Constructor Overloading" in the traditional sense (like Java or C++), but you can achieve similar results using default arguments or class methods.
The Four Pillars of OOP
Python's OOP model is built on four core principles. Understanding these will help you design better, more maintainable software:
1. Encapsulation
Encapsulation means bundling data and the methods that work on that data together inside a class — and restricting direct access to internal state. In Python, this is achieved through naming conventions (single _ for protected, double __ for name-mangled private attributes).
2. Abstraction
Abstraction hides complex implementation details and only exposes what the user needs. In the BankAccount example above, the user calls deposit() without knowing how interest calculations, fraud checks, or audit logging work internally.
3. Inheritance
A child class can inherit all attributes and methods from a parent class, then extend or override them. This avoids duplicating code across related classes. See our full guide: Python Inheritance and Operator Overloading.
4. Polymorphism
Polymorphism means different objects can respond to the same method call in their own way.
Instance Methods, Class Methods, and Static Methods
Classes can contain three types of methods. Understanding when to use each is a hallmark of clean OOP design:
For a deeper dive into all method types, see Python Class Methods and Attributes.
OOP in Practice: A Real-World Example
Let's tie everything together with a mini library management system:
Further Reading
- Python Class Methods and Attributes — master instance, class, and static methods
- Python Inheritance and Operator Overloading — take OOP to the next level
- Python Functions and Parameters — the building blocks before OOP
The official Python Classes documentation is an excellent reference for deeper exploration of the object model.
Conclusion
You now know the difference between a blueprint (Class) and a real-world entity (Object), and how to use the __init__ method to streamline your code.
In the next tutorial, we'll dive deeper into Class Variables vs. Instance Variables and explore the different types of methods!
Keep Practicing!
Try creating a `Car` class with attributes like `make`, `model`, and `year`. Print out different car objects to see how they differ!
Common OOP Mistakes in Python
1. Forgetting self in method definitions
Every instance method must have self as its first parameter. Without it, Python raises TypeError: method() takes 0 positional arguments but 1 was given when you call it on an instance. The name self is a convention, not a keyword — but deviating from it confuses every Python developer who reads the code.
2. Class attributes vs instance attributes
Defining a mutable default as a class attribute (e.g., class Dog: tricks = []) shares that list across all instances. Mutations on one instance affect all others. Always initialize mutable per-instance state in __init__: self.tricks = []. Immutable class attributes (strings, numbers, tuples) are fine to define at the class level. See the Python class documentation.
3. Not implementing __repr__ alongside __str__
__str__ controls the human-readable string (used by print()). __repr__ controls the developer-readable representation (used in the REPL and logs). If you only define __str__, repr(obj) falls back to the unhelpful <ClassName object at 0x...>. Define both: __repr__ should ideally return a string that could recreate the object.
4. Overusing inheritance instead of composition Inheritance models "is-a" relationships. If the relationship is "has-a" or "uses-a", composition is clearer: hold an instance of the other class as an attribute rather than subclassing it. Deep inheritance chains are hard to test and reason about. The principle is covered in Design Patterns: Elements of Reusable Object-Oriented Software and applies fully to Python.
5. Accessing private attributes from outside the class
Python name-mangles attributes prefixed with __ (double underscore): self.__secret becomes _ClassName__secret internally. This prevents accidental access but does not enforce true privacy. Direct access via obj._ClassName__secret still works. Use single underscore self._internal for "intended private" attributes — this signals "don't touch this" without mangling.
Frequently Asked Questions
What is the difference between a class method, static method, and instance method in Python?
An instance method receives self (the instance) as its first argument and can access instance and class state. A class method is decorated with @classmethod, receives cls (the class) as its first argument, and is typically used for alternative constructors. A static method is decorated with @staticmethod, receives no implicit first argument, and is a plain function namespaced inside the class for organizational purposes. The Python descriptor how-to explains how all three are implemented under the hood.
When should I use a dataclass instead of a regular class in Python?
Use @dataclass (Python 3.7+) when your class is primarily a data container: it auto-generates __init__, __repr__, and __eq__ from the field annotations, eliminating boilerplate. Use a regular class when you need complex initialization logic, multiple constructors, or behaviour-heavy methods that go beyond data storage. See the dataclasses documentation for field options including defaults, field(), and post_init.
What is encapsulation in Python OOP?
Encapsulation bundles data (attributes) and the methods that operate on that data inside a class, hiding internal implementation details from outside code. Python enforces encapsulation by convention rather than strict access modifiers: prefix with _ for "internal" and __ for name-mangled attributes. The goal is to prevent external code from depending on implementation details that may change. The Python tutorial on classes introduces encapsulation alongside the broader object model.
