Skip to main content
Object-Oriented Programming (OOP) lets you model real-world concepts as self-contained objects that bundle data and behaviour together. Instead of scattering functions and variables across a file, you define a class — a blueprint — and create as many instances of it as you need, each with its own data. This approach scales well for complex applications and is the basis of frameworks like FastAPI and Django.

Defining a Class

Use the class keyword followed by the class name (by convention, PascalCase) and a colon:

The __init__ Method and self

__init__ is the constructor — Python calls it automatically whenever you create a new instance. The first parameter, self, is a reference to the instance being created. Every instance attribute you want the object to have is set on self:

Creating Instances

Call the class like a function to create an instance:
Each instance is independent — changing student1.name does not affect student2.

Instance Attributes vs Class Attributes

An instance attribute lives on the object itself (set via self). A class attribute lives on the class and is shared by all instances:

Methods

Instance Methods

Instance methods take self as their first argument and can read and modify the object’s attributes:

Inheritance

Inheritance lets a child class reuse everything from a parent class and add or override behaviour as needed. This avoids duplicating code across similar classes.
Create a Dog subclass that inherits from Animal:

super()

super() gives you access to the parent class’s methods. You almost always call super().__init__() in a child’s constructor so the parent can set up its own attributes before the child adds its own.

Overriding Methods

Override a parent method in the child to change its behaviour:

Practical Example: Product and Cart

Here is a realistic example that models an e-commerce cart. It demonstrates instance attributes, methods, inheritance, and __str__ for readable printing:
Output:

When to Use OOP vs Functions

  • You have data and behaviour that naturally belong together (e.g., a BankAccount has a balance and deposit/withdraw methods).
  • You need multiple instances of the same kind of thing, each with its own state.
  • You want inheritance to avoid repeating yourself across similar types.
  • You are building a larger application where encapsulation and clear boundaries matter.
  • The logic is a simple transformation: input → output with no persistent state.
  • You are writing small scripts or one-off data processing pipelines.
  • Keeping things functional makes the code easier to test in isolation.
  • A class would only ever have one instance and no meaningful attributes.
Start with functions. Introduce a class when you notice you keep passing the same group of variables into the same set of functions — that group is begging to become a class.

Key Takeaways

Class = blueprint

A class defines the structure. Every instance = ClassName() call creates a separate object with its own copy of instance attributes.

self is the instance

Always include self as the first parameter in instance methods. Python passes the instance automatically when you call obj.method().

Inheritance shares code

Use inheritance to let child classes reuse parent code. Call super().__init__() in the child’s constructor to ensure the parent sets up correctly first.

Override, don't repeat

Override a parent method in a child class when the behaviour needs to differ. The parent method is still accessible via super().method_name().