Skip to content
Home
Python Object-Oriented Programming: Classes and Inheritance

Python Object-Oriented Programming: Classes and Inheritance

Python Python 7 min read 1470 words Beginner ExcellentWiki Editorial Team

Object-oriented programming is a paradigm that organizes code around objects — data structures that contain both data (attributes) and behavior (methods). Python supports OOP fully and, unlike some languages, does not force you to use it. You can write procedural Python, functional Python, or object-oriented Python depending on what fits your problem.

Python’s OOP is distinctive: everything is an object, classes are themselves objects (first-class citizens), and the type system uses duck typing — “if it walks like a duck and quacks like a duck, it is a duck.” This means you do not need formal interface contracts; objects are defined by what they can do, not what they inherit from.

Classes and Objects

A class is a blueprint. An object is an instance of that blueprint.

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages

    def description(self):
        return f"{self.title} by {self.author}, {self.pages} pages"

book = Book("Sapiens", "Yuval Noah Harari", 443)
print(book.description())

The class keyword defines the blueprint. The __init__ method is the constructor — Python calls it automatically when you create a new instance. The self parameter refers to the instance being created; it is always the first parameter of instance methods, though you never pass it explicitly. Naming it self is a strong convention — Python does not enforce this, but every Python programmer expects it.

Instance, Class, and Static Methods

Python classes support three types of methods, each serving a different purpose.

Instance Methods

Instance methods operate on a specific object instance. They receive self and have full access to the instance’s attributes and other methods. Most methods in a class will be instance methods — they represent the behavior of individual objects.

class Book:
    def __init__(self, title):
        self.title = title

    def read(self):  # Instance method
        return f"Reading {self.title}"

Class Methods

Class methods operate on the class itself, not on instances. They receive cls (the class) and are decorated with @classmethod. Use them for alternative constructors or operations that involve the class as a whole.

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    @classmethod
    def from_csv(cls, csv_line):
        title, author = csv_line.strip().split(",")
        return cls(title, author)

The from_csv factory method creates a Book from a CSV string. If you later subclass Book, calling from_csv on the subclass returns an instance of the subclass — this is the advantage over a standalone function.

Static Methods

Static methods are utility functions that belong to the class’s namespace but do not access the instance or class. They are decorated with @staticmethod and behave like regular functions.

class Book:
    @staticmethod
    def is_valid_title(title):
        return bool(title) and len(title) > 0

Inheritance

Inheritance lets you define a class that extends or modifies another class. The child class inherits all attributes and methods from the parent and can override or extend them.

class LibraryItem:
    def __init__(self, title, year):
        self.title = title
        self.year = year

    def summary(self):
        return f"{self.title} ({self.year})"

class Book(LibraryItem):
    def __init__(self, title, year, author, pages):
        super().__init__(title, year)
        self.author = author
        self.pages = pages

    def summary(self):
        base = super().summary()
        return f"{base} by {self.author}, {self.pages} pages"

class DVD(LibraryItem):
    def __init__(self, title, year, runtime):
        super().__init__(title, year)
        self.runtime = runtime

    def summary(self):
        base = super().summary()
        return f"{base}, {self.runtime} min"

super() gives you access to the parent class. You should always call super().__init__() in the child class to ensure the parent’s initialization runs. Method resolution order (MRO) determines which method is called when you invoke a method on an instance — Python uses the C3 linearization algorithm, which you can inspect with ClassName.__mro__.

Multiple Inheritance

Python supports multiple inheritance — a class can inherit from more than one parent. Use it sparingly; it adds complexity and can make code hard to follow.

class Flyer:
    def fly(self):
        return "Flying"

class Swimmer:
    def swim(self):
        return "Swimming"

class Duck(Flyer, Swimmer):
    pass

When method names collide, Python resolves the conflict using MRO — the first class listed in the parentheses takes priority. The super() function follows the same MRO, so chained calls with super() in a multiple-inheritance hierarchy work predictably via the cooperative multiple inheritance protocol.

Encapsulation and Name Mangling

Python does not have strict private attributes. Convention and a language feature provide access control:

  • Single underscore _name: “protected” — a convention meaning “internal use, do not touch from outside.” It is not enforced by the interpreter.
  • Double underscore __name: triggers name mangling. Python rewrites __name to _ClassName__name internally. This prevents accidental overriding in subclasses but is not true privacy.
class Book:
    def __init__(self, title, rating):
        self.title = title
        self._rating = rating       # Protected convention
        self.__isbn = None          # Name-mangled

    def set_isbn(self, isbn):
        if len(isbn) == 13:
            self.__isbn = isbn

Name mangling exists primarily to avoid accidental attribute collisions in inheritance hierarchies, not to enforce privacy. Python trusts programmers to respect conventions.

Polymorphism

Polymorphism means “many forms” — the same interface can work with different types. Python’s duck typing makes polymorphism natural: if an object has the method you call, it works, regardless of its type.

def print_summary(items):
    for item in items:
        print(item.summary())

# Works with any object that has a .summary() method
books = [Book(...), DVD(...), Magazine(...)]
print_summary(books)

In statically typed languages, polymorphism requires inheritance or interface implementation. In Python, any object with a summary() method will work — no formal contract needed. This is powerful but requires discipline: name your methods consistently and document expected interfaces.

Dunder Methods

Dunder (double underscore) methods let your objects work with Python’s built-in operations. They are the hooks that make your objects behave like native Python types.

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages

    def __str__(self):
        return f"{self.title} by {self.author}"

    def __repr__(self):
        return f"Book({self.title!r}, {self.author!r}, {self.pages})"

    def __len__(self):
        return self.pages

    def __eq__(self, other):
        if not isinstance(other, Book):
            return NotImplemented
        return (self.title, self.author) == (other.title, other.author)
MethodPurpose
__init__Constructor
__str__Human-readable string (for print(), str())
__repr__Developer-readable string (for debugging)
__len__Length (for len())
__eq__Equality comparison (==)
__lt__Less than (<)
__getitem__Indexing (obj[key])
__call__Make object callable (obj())
__enter__ / __exit__Context manager (with statement)

Override __repr__ whenever you write a class — it makes debugging dramatically easier. The convention is that __repr__ should return a string that could recreate the object if passed to eval().

Composition vs Inheritance

Inheritance models an “is-a” relationship: a Book is a LibraryItem. Composition models a “has-a” relationship: a Library has Books. Favor composition over inheritance.

# Inheritance (is-a)
class Ebook(Book):
    def __init__(self, title, author, format):
        super().__init__(title, author)
        self.format = format

# Composition (has-a)
class Library:
    def __init__(self):
        self.books = []
        self.dvds = []

    def add_book(self, book):
        self.books.append(book)

Inheritance creates tight coupling — changes to the parent class can break child classes. Composition is more flexible and easier to test. A good rule of thumb: if you are inheriting primarily to reuse code rather than to model a genuine subtype relationship, use composition instead.

Best Practices

Dataclasses for Simple Objects

Python 3.7+ dataclasses reduce boilerplate for classes that primarily hold data:

from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    pages: int
    year: int = 2024

Dataclasses automatically generate __init__, __repr__, __eq__, and __hash__ based on the declared fields. Use them for value objects and data containers; use regular classes when you need custom behavior or inheritance-heavy designs.

Keep It Simple

  • Prefer plain functions and modules over single-method classes
  • Use properties (@property) instead of getter/setter methods
  • Keep inheritance hierarchies shallow — no more than two or three levels
  • Write small classes with a single responsibility
class Book:
    @property
    def age(self):
        return 2024 - self.year

Abstract Base Classes

For defining interfaces that subclasses must implement, use abc:

from abc import ABC, abstractmethod

class LibraryItem(ABC):
    @abstractmethod
    def summary(self):
        pass

Related: Learn Python error handling and list comprehensions.

Composition Over Inheritance

The principle “favor composition over inheritance” advises building classes by combining objects rather than extending base classes. Composition provides more flexibility at runtime — you can swap components, add behaviors dynamically, and avoid the fragile base class problem where changes to a parent class break child classes. Django’s class-based views exemplify this with mixins, while the strategy pattern uses composition to swap algorithms at runtime.

Abstract Base Classes

Python’s abc module provides abstract base classes for defining interfaces. Decorate methods with @abstractmethod to require subclasses to implement them. ABCs can include concrete methods alongside abstract ones, providing shared functionality. The collections.abc module provides abstract base classes for containers, iterators, and sequences.

FAQ

What is the most important thing to remember? Focus on understanding the core concepts thoroughly before moving to advanced topics. Mastery comes from practice, not just reading.

How long does it take to learn this? The timeline varies by individual, but consistent daily practice of 30-60 minutes yields visible progress within weeks.

What are common mistakes beginners make? The most frequent errors include skipping fundamentals, not testing assumptions, and trying to learn too many things simultaneously.

Section: Python 1470 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top