Class inheritance

Class inheritance allows us to create a new class (called a child class or subclass) based on an existing class (called a parent class or superclass). The child class inherits the attributes and methods of the parent class, allowing us to reuse and extend the functionality of the parent class.

Let's take an example of a `Vehicle` class and a `Car` class to understand how class inheritance works:

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

    def start_engine(self):
        print("Engine started!")

    def stop_engine(self):
        print("Engine stopped!")


class Car(Vehicle):
    def drive(self):
        print("Car is being driven!")
In this code, we define a `Vehicle` class with attributes `make`, `model`, and `year`, and methods `start_engine` and `stop_engine`. We also define a `Car` class that inherits from the `Vehicle` class using the syntax `class Car(Vehicle):`.

The `Car` class inherits the attributes and methods of the `Vehicle` class. It also defines its own method called `drive`, which is specific to cars.

To create an object of the `Car` class and access its attributes and methods, we can do the following:

my_car = Car("Toyota", "Camry", 2022)
print(my_car.make)  # Output: Toyota
print(my_car.model)  # Output: Camry
print(my_car.year)  # Output: 2022

my_car.start_engine()  # Output: Engine started!
my_car.drive()  # Output: Car is being driven!
my_car.stop_engine()  # Output: Engine stopped!
Toyota
Camry
2022
Engine started!
Car is being driven!
Engine stopped!
In this code, we create an object of the `Car` class called `my_car`. We can access the attributes `make`, `model`, and `year` inherited from the `Vehicle` class, as well as call the methods `start_engine`, `drive`, and `stop_engine` inherited from the `Vehicle` class.

Class inheritance allows us to create specialized classes that inherit and extend the functionality of a more general class. It promotes code reuse, modularity, and flexibility in your programs.

You can also override methods inherited from the parent class in the child class to provide custom behavior. This allows us to modify or extend the functionality of the inherited methods. Here's an example:

class Car(Vehicle):
    def drive(self):
        print("Car is being driven!")

    def stop_engine(self):
        print("Car engine stopped!")  # Override the stop_engine method
In this code, we override the `stop_engine` method in the `Car` class to provide a custom implementation specific to cars.