Course topics

By WebNest Studio Engineering Team

Python Tutorial

Object-Oriented Programming in Python

Object-oriented programming (OOP) models a program as a collection of objects, each bundling together data (attributes) and behavior (methods) that operate on that data. Python supports OOP fully, and while it doesn't force you to use classes for everything the way some languages do, classes are central to how most non-trivial Python code — including nearly the entire standard library — is organized.

Understanding classes, instances, inheritance, and Python's special "dunder" (double-underscore) methods is the foundation for reading and writing idiomatic Python at any real scale.

Why it matters

Larger programs are organised around objects: a user, an order, a database connection. Django models, FastAPI schemas and most libraries you will use are classes, so reading and writing them is unavoidable. Object-oriented design is also the subject of a large share of Python interview questions.

Classes, __init__, and self

A class is a blueprint for creating objects; each object created from it is called an instance. The __init__ method is a special method automatically called when a new instance is created — it's where you typically set up initial attribute values. Every instance method's first parameter is conventionally named self, and it refers to the specific instance the method was called on; Python passes it automatically, so obj.method(arg) is really calling ClassName.method(obj, arg) behind the scenes.

Inheritance

A class can inherit from another class by listing it in parentheses after the class name, as in class Dog(Animal):. The subclass (Dog) automatically gets all the attributes and methods of the superclass (Animal) and can override any of them, or extend the parent's behavior by calling super().__init__(...) or super().method(...) from within its own definition. This lets you share common behavior across related classes while still specializing individual ones.

Dunder (Magic) Methods

Methods surrounded by double underscores, like __init__, integrate a class with Python's built-in syntax and functions rather than requiring special-case code. Defining __str__ controls what print(obj) and str(obj) display; __repr__ controls the unambiguous developer-facing representation shown in a debugger or REPL. Defining __eq__ controls what == means for your objects (by default, two instances are only equal if they are literally the same object in memory). Other common dunder methods include __len__ (for len(obj)), __lt__ (for < comparisons), and __add__ (for +).

Examples

A class hierarchy using __init__, inheritance, and super()

Python
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} makes a sound."

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # reuse Animal's setup logic
        self.breed = breed

    def speak(self):             # override the parent's method
        return f"{self.name} barks."

generic = Animal("Creature")
rex = Dog("Rex", "Labrador")
print(generic.speak())
print(rex.speak())
print(rex.breed)
Output
Creature makes a sound.
Rex barks.
Labrador

__str__ and __eq__ for readable printing and value-based comparison

Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)
print(p1 == p2)      # uses __eq__, compares values, not identity
print(p1 is p2)      # identity check, always False for separate objects
Output
Point(1, 2)
True
False

Common Mistakes

  • Forgetting "self" as the first parameter of an instance method, causing a TypeError about the number of arguments when the method is called.
  • Using "==" and expecting value equality before defining __eq__ — by default it falls back to identity comparison ("is"), which is almost never what you want for custom objects.
  • Forgetting to call super().__init__() in a subclass, so inherited attributes set up by the parent class are never actually initialized.
  • Defining __str__ but expecting it to also control the REPL/debugger representation — that is __repr__'s job, and defining only one of them can produce inconsistent output.

Exercise

Write a Book class with a title and a price. Printing a book should show its title and price, and two books should be equal when they have the same title.

Expected output: Clean Code (450) then True

Python · starter code
class Book:
    # TODO: __init__ storing title and price
    # TODO: __str__ returning e.g. "Clean Code (450)"
    # TODO: __eq__ comparing titles
    pass


print(Book("Clean Code", 450))
print(Book("Clean Code", 450) == Book("Clean Code", 300))
Show hints
  • __str__ must return a string; print calls it automatically.
  • __eq__(self, other) returns self.title == other.title.
Show solution
Python · solution
class Book:
    def __init__(self, title, price):
        self.title = title
        self.price = price

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

    def __eq__(self, other):
        return self.title == other.title


print(Book("Clean Code", 450))
print(Book("Clean Code", 450) == Book("Clean Code", 300))

Quick quiz

1. What is self in a method?

  • A. A keyword that means the class
  • B. The parent class
  • C. A global variable
  • D. The object the method was called on
Show answer

D. Python passes the object as the first argument. The name self is a convention.

2. When is __init__ called?

  • A. Each time a new object of the class is created
  • B. When the class is defined
  • C. When the object is printed
  • D. When the program ends
Show answer

A. It sets up the attributes of the new object.

3. What does super().__init__(name) do in a subclass?

  • A. Creates a second object
  • B. Runs the parent class's __init__ for this object
  • C. Deletes the parent
  • D. Calls the subclass again
Show answer

B. It lets the parent set up its own attributes before the subclass adds more.

Interview questions

What are the four main principles of object-oriented programming?

Show answer

Encapsulation: keeping data and the methods that work on it together, and hiding internal details. Abstraction: exposing what an object does without how it does it. Inheritance: a class reusing and extending another class. Polymorphism: different classes responding to the same method call in their own way.

What is the difference between a class and an object?

Show answer

A class is a definition: it describes which attributes and methods its objects will have. An object, or instance, is a concrete thing created from the class, with its own values for those attributes. One class can produce any number of objects, each independent of the others.

Key Points to Remember

  • A class is a blueprint; an instance is a concrete object created from it via __init__.
  • self refers to the specific instance a method is operating on, and Python passes it automatically.
  • Inheritance lets a subclass reuse and override a superclass's behavior, often via super().
  • Dunder methods like __str__, __eq__, and __len__ integrate custom classes with Python's built-in syntax.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs Python in your browser. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering