Python lets you write a working script without ever defining a class. So why bother with objects at all? Because the moment your program grows past a few hundred lines, you start passing the same clump of variables into every function — a user's name, email and permissions; a connection's host, port and timeout. Object-oriented programming is what you reach for when data and the behaviour that operates on it clearly belong together.
This guide walks through every core OOP concept in Python, with runnable examples.
Classes and Objects
A class is a blueprint. An object is a concrete thing built from that blueprint.
Two things confuse newcomers here.
__init__ is not a constructor in the C++ or Java sense — the object already exists by the time it runs. It is an initialiser that fills in the attributes. (The actual constructor is __new__, which you will rarely need to touch.)
self is just the instance, passed in explicitly. When you write buddy.bark(), Python translates it to Dog.bark(buddy). Python chose to make this visible rather than hide it behind an implicit this. The name self is a convention, not a keyword — but break it and every Python developer who reads your code will wince.
Attributes: Instance vs Class
Attributes defined inside __init__ belong to each instance. Attributes defined in the class body are shared by all instances.
Lookup goes instance first, then class. That means emp.raise_percent reads the shared value, but emp.raise_percent = 1.06 creates a new instance attribute that shadows it, leaving everyone else untouched. To change it for everybody, assign to the class: Employee.raise_percent = 1.06.
items = [] in the class body is shared by every instance ever created — one basket's groceries show up in all of them. Assign self.items = [] inside __init__ instead.Methods: Instance, Class and Static
Python has three kinds of methods, distinguished by what gets passed as the first argument.
An instance method receives the object as self. A class method receives the class as cls and is the idiomatic way to write alternative constructors — because cls respects subclasses, Manager.from_string(...) returns a Manager, not an Employee. A static method receives nothing special; it is a plain function parked inside the class because it belongs there conceptually.
Encapsulation
Encapsulation means bundling data with the methods that guard it, and keeping the internals private so you can change them later without breaking callers.
Python has no private keyword. It uses convention instead:
name— public, use freely._name— a single underscore signals “internal, don't touch.” Nothing enforces it. This is the famous we're all consenting adults here philosophy.__name— a double underscore triggers name mangling: inside classFoothe attribute is stored as_Foo__name. That exists to prevent accidental clashes in subclasses, not to stop determined access.
The real tool for encapsulation is property, which lets you expose an attribute publicly while running code behind the scenes:
Inheritance
Inheritance lets a class reuse and extend another class.
super() delegates to the next class in the lookup chain. Always prefer it over calling Animal.__init__(self, name) directly — with multiple inheritance, the hardcoded version silently skips classes.
Python supports multiple inheritance, and resolves ambiguity with the Method Resolution Order (MRO), computed by the C3 linearisation algorithm:
Depth is a liability. Most experienced Python code uses shallow hierarchies plus mixins — small classes that add one focused capability (JSONSerializableMixin, TimestampMixin) and are never instantiated on their own.
Polymorphism
Polymorphism means the same call works on different types. Python gets there three ways.
1. Method overriding
As above: speak() does something different for every subclass.
2. Duck typing
This is the Python-native flavour. There is no need for a shared base class — if an object has the method, it works:
None of these need to inherit from anything common. If it walks like a duck and quacks like a duck, it's a duck.
3. Operator overloading
Dunder (double-underscore) methods hook your class into Python's built-in syntax:
Useful ones to know: __str__ (user-facing text), __repr__ (debug text), __len__, __getitem__, __iter__, __contains__, __call__, and __enter__ / __exit__ for context managers.
__eq__ sets __hash__ to None, which makes your instances unhashable — they can no longer go in a set or be used as dict keys unless you define __hash__ too.Abstraction
Abstraction means exposing what something does while hiding how. The abc module turns that into an enforced contract:
An abstract base class cannot be instantiated, and any subclass that fails to implement every abstract method cannot be instantiated either — so the error surfaces at object creation, not deep in production.
Composition Over Inheritance
Inheritance models “is a.” Composition models “has a,” and it is usually the better default:
A Car is not a kind of Engine. Composition keeps classes loosely coupled and swappable — you can hand Car a DieselEngine or a mock engine in tests without touching any hierarchy. Reach for inheritance only when the subclass genuinely substitutes for the parent everywhere the parent is used.
Less Boilerplate with Dataclasses
For classes that mostly hold data, dataclasses generates __init__, __repr__ and __eq__ for you:
Note field(default_factory=list) — that is the mutable-default trap from earlier, handled properly.
Wrapping Up
The four pillars — encapsulation, inheritance, polymorphism, abstraction — all show up in Python, but with a distinctly Python accent. Privacy is a convention backed by properties rather than a keyword. Polymorphism leans on duck typing more than on rigid class hierarchies. Abstraction is opt-in through abc.
Comments
Post a Comment