Skip to content

OOP - Classes and Objects

A class is a blueprint for creating objects.

class Car:
    pass


audi = Car()
bmw = Car()

print(type(audi))
print(audi)
print(type(bmw))
print(bmw)

<__main__.Car object at 0x7f0b7c1db920>

<__main__.Car object at 0x7f0b7c1dba10>

class Dog:
    def __init__(self, name, age):  # Constructor
        self.name = name
        self.age = age

    def bark(self):
        return f"{self.name} says Woof!"


dog1 = Dog("Buddy", 3)

dog1.bark()

'Buddy says Woof!'

Inheritance

Single Inheritance

class Car:
    """Superclass for all cars."""

    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def start(self):
        return f"{self.year} {self.make} {self.model} is starting."

    def stop(self):
        return f"{self.year} {self.make} {self.model} is stopping."


car1 = Car("Toyota", "Corolla", 2020)
car1.start()

'2020 Toyota Corolla is starting.'

class Tesla(Car):
    def __init__(self, model, year, battery_size=75):
        super().__init__("Tesla", model, year)
        self.battery_size = battery_size

    def charge(self):
        return f"{self.year} {self.make} {self.model} is charging with a {self.battery_size}-kWh battery."


tesla1 = Tesla("Model 3", 2021)
tesla1.start()
tesla1.charge()

'2021 Tesla Model 3 is charging with a 75-kWh battery.'

Multiple Inheritance

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

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


class Pet:
    def __init__(self, owner):
        self.owner = owner

    def get_owner(self):
        return f"{self.owner} is the owner of this pet."


class Dog(Animal, Pet):
    def __init__(self, name, owner):
        Animal.__init__(self, name)
        Pet.__init__(self, owner)

    def speak(self):
        return f"{self.name} says Woof!"


dog1 = Dog("Buddy", "Alice")
print(dog1.speak())
dog1.get_owner()

Buddy says Woof!

'Alice is the owner of this pet.'

Polymorphism

It provides a way to perform a single action in different forms

Method overriding allows a child class to provide a specific implementation of a method that is already defined in its parent class

class Animal:
    def speak(self):
        return "Animal sound"


class Dog(Animal):
    def speak(self):
        return "Dog barks"


class Cat(Animal):
    def speak(self):
        return "Cat meows"


def animal_sound(animal):
    """Polymorphic function to get the sound of an animal."""
    return animal.speak()


dog = Dog()
cat = Cat()
print(animal_sound(dog))
print(animal_sound(cat))

Dog barks

Cat meows

class Shape:
    def area(self):
        raise NotImplementedError("Subclasses must implement this method")


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * (self.radius**2)


def print_area(shape):
    print(f"The area is: {shape.area()}")


rectangle = Rectangle(5, 10)
circle = Circle(7)
print_area(rectangle)
print_area(circle)

The area is: 50

The area is: 153.86

from abc import ABC, abstractmethod


class Vehicle(ABC):
    @abstractmethod
    def start(self):
        pass

    @abstractmethod
    def stop(self):
        pass


class Car(Vehicle):
    def start(self):
        return "Car is starting."

    def stop(self):
        return "Car is stopping."


class motorcycle(Vehicle):
    def start(self):
        return "Motorcycle is starting."

    def stop(self):
        return "Motorcycle is stopping."


def start_vehicle(vehicle):
    print(vehicle.start())


def stop_vehicle(vehicle):
    print(vehicle.stop())


car = Car()
motorcycle = motorcycle()
start_vehicle(car)
start_vehicle(motorcycle)
stop_vehicle(car)
stop_vehicle(motorcycle)

Car is starting.

Motorcycle is starting.

Car is stopping.

Motorcycle is stopping.

Encapsulation

Encapsulation involves bundling data and methods that operate on the data within a single unit

Public, private and protected or access modifiers

  • Public: accessible by every other piece of code
  • Protected: accessible by derived classes
  • Private: accessible only by the objects itself
class Person:
    def __init__(self, name, age):
        self.name = name  # Public attribute
        self._age = age  # Protected attribute
        self.__ssn = "123-45-6789"  # Private attribute

    def get_ssn(self):
        return self.__ssn  # Method to access private attribute

    def set_age(self, age):
        if age > 0:
            self._age = age
        else:
            raise ValueError("Age must be positive")

Abstraction

Abstraction is the concept of hiding the complex implementation details and showing only the necessary features of an object

Magic Methods or Dunder Methods

Magic methods are pre-defined methods in Pyhton that you can override to change the behavior of your objects.

  • __init__
  • __str__
  • __repr__
  • __len__
  • __getitem__
  • __setitem__
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"

    def __repr__(self):
        return f"Person(name={self.name}, age={self.age})"


bob = Person("Bob", 30)
print(bob)  # Calls __str__
print(repr(bob))  # Calls __repr__

Person(name=Bob, age=30)

Person(name=Bob, age=30)

Operator overloading

Operator overloading allows you to define the behavior of operators (+, -, *, ...) for custom objects

  • __add__: add (+)
  • __sub__: subtraction(-)
  • __mul__: multiplication (*)
  • __truediv__: division (/)
  • __eq__: equals (==)
  • __lt__: less than (<)
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

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

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


v1 = Vector(2, 3)
v2 = Vector(5, 7)
v3 = v1 + v2
print(v3)

v4 = v2 - v1
print(v4)

print(v1 == v2)
print(v1 == Vector(2, 3))

Vector(7, 10)

Vector(3, 4)

False

True

Custom Exceptions

class Error(Exception):
    """Base class for custom exceptions."""

    pass


class ValidationError(Error):
    """Raised when a validation error occurs."""

    def __init__(self, message):
        self.message = message
        super().__init__(self.message)