Python - Classes

9.
What is the purpose of the __str__ method in a class?

In Python, the __str__ method is used to define a human-readable representation of an object. When the str() function is called on an object, or when the print() function is used with the object, the __str__ method is automatically called. This method should return a string that represents the object in a clear and concise way.

Let's illustrate the purpose of the __str__ method with an example:

class Student:
    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def __str__(self):
        return f"Student: {self.name}, Age: {self.age}, Grade: {self.grade}"

# Creating an object of the Student class
student1 = Student("Alice", 20, "A")

# Calling the __str__ method implicitly
print(student1)

In this example, the Student class has an __init__ method to initialize the name, age, and grade attributes. The __str__ method returns a formatted string representing the student's information.

When we print the student1 object, the __str__ method is automatically called, providing a human-readable representation of the object.

Output:

Student: Alice, Age: 20, Grade: A

10.
Explain the concept of inheritance in Python classes.

In Python, inheritance is a mechanism that allows a new class to inherit attributes and methods from an existing class. The existing class is referred to as the base class or parent class, and the new class is called the derived class or child class. Inheritance promotes code reuse and allows the child class to extend or override the functionality of the parent class.

Let's demonstrate the concept of inheritance with an example:

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

    def speak(self):
        return "Some generic sound"

# Derived class (Child class)
class Dog(Animal):
    def bark(self):
        return "Woof!"

# Derived class (Child class)
class Cat(Animal):
    def meow(self):
        return "Meow!"

# Creating objects of the derived classes
dog = Dog("Buddy")
cat = Cat("Whiskers")

# Accessing methods from the base class
print(f"{dog.name} says: {dog.speak()}")
print(f"{cat.name} says: {cat.speak()}")

# Accessing methods from the child classes
print(f"{dog.name} barks: {dog.bark()}")
print(f"{cat.name} meows: {cat.meow()}")

In this example, the Animal class is the base class with a constructor and a speak method. The Dog and Cat classes are derived classes that inherit from the Animal class. The Dog class introduces a new method bark, and the Cat class introduces a new method meow.

We create objects of the derived classes and demonstrate the use of inherited methods from the base class as well as the new methods introduced in the derived classes.

Outputs:

Buddy says: Some generic sound
Whiskers says: Some generic sound
Buddy barks: Woof!
Whiskers meows: Meow!

11.
How do you achieve method overriding in Python classes?

Method overriding in Python allows a derived class to provide a specific implementation for a method that is already defined in its base class. This allows the child class to customize or extend the behavior of the inherited method.

Let's demonstrate method overriding with an example:

class Shape:
    def draw(self):
        return "Drawing a generic shape"

# Derived class (Child class)
class Circle(Shape):
    def draw(self):
        return "Drawing a circle"

# Derived class (Child class)
class Square(Shape):
    def draw(self):
        return "Drawing a square"

# Creating objects of the derived classes
circle = Circle()
square = Square()

# Calling overridden methods
print(circle.draw())
print(square.draw())

In this example, the Shape class has a method called draw. The Circle and Square classes are derived from the Shape class and override the draw method with their own specific implementations.

When we create objects of the derived classes and call the draw method, the overridden method in each class is executed, providing a specialized behavior.

Output:

Drawing a circle
Drawing a square

12.
What is the significance of the super() function in inheritance?

In Python, the super() function is used to call a method from the parent class in the context of the derived class. It is often used in the constructor (__init__) of the child class to invoke the constructor of the parent class. This allows the child class to perform its own initialization while reusing the functionality of the parent class.

Let's illustrate the significance of the super() function with an example:

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

    def make_sound(self):
        return "Some generic sound"

# Derived class (Child class)
class Dog(Animal):
    def __init__(self, name, breed):
        # Using super() to call the constructor of the parent class
        super().__init__(name)
        self.breed = breed

    def make_sound(self):
        # Using super() to call the method from the parent class
        parent_sound = super().make_sound()
        return f"{self.name} ({self.breed}) barks: Woof! {parent_sound}"

# Creating an object of the Dog class
dog = Dog("Buddy", "Labrador")

# Calling overridden method
result = dog.make_sound()

# Displaying the result
print(result)

In this example, the Animal class has a constructor and a method called make_sound. The Dog class is derived from Animal and has its own constructor that uses super() to call the constructor of the parent class. It also overrides the make_sound method and uses super() to invoke the method from the parent class while extending its behavior.

Output:

Buddy (Labrador) barks: Woof! Some generic sound