Object-Oriented Programming in Python : Part 2
Tags: oop, python, codenewbie, programming
Published: 2022-04-17 10:22:57

**3. Polymorphism**

The term 'polymorphism' comes from the Greek language and means 'something that takes on multiple forms.'

Polymorphism refers to a subclass's ability to adapt a method that already exists in its superclass to meet its needs. To put it another way, a subclass can use a method from its superclass as is or modify it as needed.

```
class Academic(Book):
    def __init__(self, title, quantity, author, price, branch):
        super().__init__(title, quantity, author, price)
        self.branch = branch

    def __repr__(self):
        return f"Book: {self.title}, Branch: {self.branch}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
```

The Book superclass has a specific method called __repr__. This method can be used by subclass Novel so that it is called whenever an object is printed.

The Academic subclass, on the other hand, is defined with its own __repr__ special function in the example code above. The Academic subclass will invoke its own method by suppressing the same method present in its superclass, thanks to polymorphism.


```
novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
novel1.set_discount(0.20)

academic1 = Academic('Python Foundations', 12, 'PSF', 655, 'IT')

print(novel1)
print(academic1)
```

**Output:**

```
Book: Two States, Quantity: 20, Author: Chetan Bhagat, Price: 160.0
Book: Python Foundations, Branch: IT, Quantity: 12, Author: PSF, Price: 655
```

**4. Abstraction**

Abstraction isn't supported directly in Python. Calling a magic method, on the other hand, allows for abstraction.

If an abstract method is declared in a superclass, subclasses that inherit from the superclass must have their own implementation of the method.

A superclass's abstract method will never be called by its subclasses. But the abstraction aids in the maintenance of a similar structure across all subclasses.

In our parent class Book, we have defined the __repr__ method. Let's make that method abstract, forcing every subclass to compulsorily have its own __repr__ method.


```
from abc import ABC, abstractmethod


class Book(ABC):
    def __init__(self, title, quantity, author, price):
        self.title = title
        self.quantity = quantity
        self.author = author
        self.__price = price
        self.__discount = None

    def set_discount(self, discount):
        self.__discount = discount

    def get_price(self):
        if self.__discount:
            return self.__price * (1-self.__discount)
        return self.__price

    @abstractmethod
    def __repr__(self):
        return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"


class Novel(Book):
    def __init__(self, title, quantity, author, price, pages):
        super().__init__(title, quantity, author, price)
        self.pages = pages


class Academic(Book):
    def __init__(self, title, quantity, author, price, branch):
        super().__init__(title, quantity, author, price)
        self.branch = branch

    def __repr__(self):
        return f"Book: {self.title}, Branch: {self.branch}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"


novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
novel1.set_discount(0.20)

academic1 = Academic('Python Foundations', 12, 'PSF', 655, 'IT')

print(novel1)
print(academic1)
```

In the above code, we have inherited the ABC class to create the Book class. We've made the __repr__ method abstract by adding the @abstractmethod decorator.

While creating the Novel class, we intentionally missed the implementation of the __repr__ method to see what happens.


**Output:**

```
Traceback (most recent call last):
  File "C:\Users\ashut\Desktop\Test\hello\test.py", line 40, in <module>
    novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
TypeError: Can't instantiate abstract class Novel with abstract method __repr__
```

We get a TypeError saying we cannot instantiate object of the Novel class. Let's add the implementation of the __repr__ method and see what happens now.


```
class Novel(Book):
    def __init__(self, title, quantity, author, price, pages):
        super().__init__(title, quantity, author, price)
        self.pages = pages

    def __repr__(self):
        return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
```

**Output:**

```
Book: Two States, Quantity: 20, Author: Chetan Bhagat, Price: 160.0
Book: Python Foundations, Branch: IT, Quantity: 12, Author: PSF, Price: 655
```
Now it works fine.

**Method Overloading:**

The concept of method overloading is found in almost every well-known programming language that follows object-oriented programming concepts. It simply refers to the use of many methods with the same name that take various numbers of arguments within a single class.

Let's now understand method overloading with the help of the following code:

```
class OverloadingDemo:
    def add(self, x, y):
        print(x+y)

    def add(self, x, y, z):
        print(x+y+z)


obj = OverloadingDemo()
obj.add(2, 3)
```

**Output:**

```
Traceback (most recent call last):
  File "C:\Users\ashut\Desktop\Test\hello\setup.py", line 10, in <module>
    obj.add(2, 3)
TypeError: add() missing 1 required positional argument: 'z'
```

You're probably wondering why this happened. As a result, the error is displayed because Python only remembers the most recent definition of add(self, x, y, z), which takes three parameters in addition to self. As a result, three arguments must be passed to the add() method when it is called. To put it another way, it disregards the prior definition of add().

Thus, Python doesn't support Method Overloading by default.


**Method Overriding:**

When a method with the same name and arguments is used in both a derived class and a base or super class, we say that the derived class method "overrides" the method provided in the base class.

When the overridden method gets called, the derived class's method is always invoked. The method that was used in the base class is now hidden.

```
class ParentClass:
    def call_me(self):
        print("I am parent class")


class ChildClass(ParentClass):
    def call_me(self):
        print("I am child class")

pobj = ParentClass()
pobj.call_me()

cobj = ChildClass()
cobj.call_me()
```

**Output:**

```
I am parent class
I am child class
```

In the above code, when the call_me() method was called on the child object, the call_me() from the child class was invoked. We can invoke the parent class's call_me() method from the child class using super(), like this:

```
class ParentClass:
    def call_me(self):
        print("I am parent class")


class ChildClass(ParentClass):
    def call_me(self):
        print("I am child class")
        super().call_me()

pobj = ParentClass()
pobj.call_me()

cobj = ChildClass()
cobj.call_me()
```

**Output:**

```
I am parent class
I am child class
I am parent class
```

**Wrapping Up:**

In this article, we covered what classes and objects mean. We also covered the four pillars of the Object-Oriented Programming.

Apart from that we also touched two important topics – Method Overloading and Method Overriding.

Thanks for reading!



[Object-Oriented Programming in Python : Part 1](https://dev.to/sandeepk27/object-oriented-programming-in-python-part-1-4b25)

[Top Websites to learn Python](https://dev.to/sandeepk27/top-websites-to-learn-python-for-free-1j1l)

[How to install python in windows 10](https://dev.to/sandeepk27/how-to-install-python-310-2dik)

[Python cheat sheet](https://dev.to/sandeepk27/python-cheat-sheet-for-beginners-part-1-5baj)

[Best IDE's for Python](https://dev.to/sandeepk27/best-ides-for-python-362k)