Python tutorials > Object-Oriented Programming (OOP) > Polymorphism > What is polymorphism?

What is polymorphism?

Polymorphism, derived from the Greek words 'poly' (many) and 'morph' (form), is a fundamental concept in object-oriented programming (OOP). It allows objects of different classes to respond to the same method call in their own specific way. In essence, it's the ability of a single interface to represent different underlying forms or data types. Polymorphism promotes code reusability, flexibility, and extensibility, making it easier to maintain and evolve complex software systems. This tutorial will explore polymorphism in Python with clear examples.

Understanding Polymorphism

Polymorphism allows you to write code that can work with objects of different classes without knowing their specific type in advance. This is achieved through two main mechanisms in Python: Duck Typing and Inheritance. Duck typing focuses on the object's behavior (methods), while inheritance focuses on the object's type (class hierarchy).

Duck Typing

Duck Typing is a style of dynamic typing where an object's suitability is determined by the presence of certain methods and properties, rather than the object's type. The phrase 'If it walks like a duck and quacks like a duck, then it is a duck' encapsulates this concept. Python heavily relies on duck typing.

Duck Typing - Example

In this example, the do_something function accepts any object that has quack and fly methods. It doesn't care if the object is a Duck or a Person; it only cares that the object can perform those actions. This illustrates duck typing, where the object's behavior determines its suitability.

class Duck:
    def quack(self):
        print("Quack, quack!")

    def fly(self):
        print("Duck is flying")

class Person:
    def quack(self):
        print("Person imitating a duck")

    def fly(self):
        print("Person waving arms, trying to fly!")

def do_something(animal):
    animal.quack()
    animal.fly()

duck = Duck()
person = Person()

do_something(duck)
do_something(person)

Inheritance and Polymorphism

Inheritance allows you to create new classes (derived classes or subclasses) that inherit attributes and methods from existing classes (base classes or superclasses). Polymorphism, in the context of inheritance, allows a derived class to override methods of its base class, providing specialized implementations.

Inheritance Polymorphism - Example

Here, Animal is the base class, and Dog and Cat are derived classes. Each derived class overrides the speak method to provide its own specific implementation. The animal_sound function can accept any object that is an instance of Animal or any of its subclasses, and it will call the appropriate speak method based on the object's actual type.

class Animal:
    def speak(self):
        print("Generic animal sound")

class Dog(Animal):
    def speak(self):
        print("Woof!")

class Cat(Animal):
    def speak(self):
        print("Meow!")

def animal_sound(animal):
    animal.speak()

animal = Animal()
dog = Dog()
cat = Cat()

animal_sound(animal)
animal_sound(dog)
animal_sound(cat)

Method Overriding

Method overriding is a key aspect of polymorphism with inheritance. It allows a subclass to provide a specific implementation for a method that is already defined in its superclass. This allows the subclass to customize the behavior of the inherited method to suit its own needs.

concepts behind the snippet

Abstraction: Polymorphism leverages abstraction by providing a unified interface for interacting with different objects.
Inheritance: When combined with inheritance, polymorphism enables subclasses to provide specialized implementations of inherited methods.
Dynamic Binding: In Python, method calls are resolved at runtime, which means the specific method to be executed is determined by the object's actual type, not its declared type. This is known as dynamic binding or late binding and is essential for polymorphism to work.

Real-Life Use Case Section

Consider a graphics application. You might have a base class called Shape with subclasses like Circle, Rectangle, and Triangle. Each shape class would have its own draw() method. The application can then store a collection of Shape objects and iterate through them, calling the draw() method on each object. Polymorphism ensures that the correct draw() method is called for each specific shape, even though the application only knows it's dealing with Shape objects. This allows for easy addition of new shape types without modifying the core drawing logic.

Best Practices

Use Abstract Base Classes (ABCs): Use ABCs to define abstract methods that subclasses must implement. This enforces a specific interface and ensures that subclasses adhere to the expected behavior.
Favor Composition over Inheritance: In some cases, composition (creating objects that contain other objects) can be a more flexible alternative to inheritance. Composition allows you to achieve polymorphism without the tight coupling that inheritance can create.
Design for Extensibility: When designing your classes, consider how they might be extended in the future. Use polymorphism to create flexible and adaptable code that can accommodate new requirements.

Interview Tip

When discussing polymorphism in an interview, be sure to explain the different ways it's achieved in Python (Duck Typing and Inheritance). Provide clear examples and be prepared to discuss the benefits and drawbacks of each approach. Also, be prepared to explain how polymorphism contributes to code reusability, flexibility, and maintainability.

When to use them

Use polymorphism when you need to write code that can operate on objects of different types in a uniform way. This is particularly useful when dealing with collections of objects, when you want to add new types without modifying existing code, or when you want to create a flexible and extensible system.

Memory footprint

The memory footprint of polymorphism itself is generally small. The primary impact on memory comes from the objects themselves and the storage of their methods. Using inheritance may lead to a slightly larger memory footprint due to the storage of inherited attributes and methods in each subclass object. Duck typing doesn't inherently increase memory usage; it depends on the data structures used in the classes themselves.

Alternatives

Conditional Statements: You could use conditional statements (if/elif/else) to handle different object types explicitly. However, this approach can lead to less maintainable and less extensible code.
Function Overloading (Not Directly Supported in Python): In some languages, you can overload functions, providing multiple definitions for the same function name with different parameter types. Python doesn't directly support function overloading based on parameter types, but you can achieve similar results using default argument values or variable-length argument lists (*args and **kwargs).

pros

Code Reusability: Polymorphism allows you to write generic code that can work with multiple object types, reducing code duplication.
Flexibility: Polymorphism makes your code more flexible and adaptable to change. You can easily add new object types without modifying existing code.
Maintainability: Polymorphic code is easier to maintain and understand because it is more modular and less coupled.
Extensibility: Polymorphism allows you to easily extend your code to support new functionality without breaking existing code.

cons

Increased Complexity: Polymorphism can sometimes make code more complex, especially when dealing with complex inheritance hierarchies.
Potential for Runtime Errors: With Duck Typing, if an object doesn't actually implement the expected methods, you might encounter runtime errors. Using Abstract Base Classes can help mitigate this risk.
Debugging Challenges: Polymorphism can make debugging more challenging because the specific code that is executed depends on the object's runtime type.

FAQ

  • What is the difference between static polymorphism and dynamic polymorphism?

    Static polymorphism (also known as compile-time polymorphism) refers to polymorphism that is resolved at compile time. Python doesn't directly support static polymorphism in the same way as languages like C++ (e.g., through function overloading). Dynamic polymorphism (also known as runtime polymorphism) refers to polymorphism that is resolved at runtime. Python primarily uses dynamic polymorphism through duck typing and method overriding.
  • How does polymorphism relate to interfaces?

    An interface defines a set of methods that a class must implement. Polymorphism allows you to treat objects of different classes that implement the same interface in a uniform way. In Python, interfaces are often implicitly defined through duck typing or explicitly defined using Abstract Base Classes (ABCs).
  • Is polymorphism only applicable to object-oriented programming?

    While polymorphism is a core concept in object-oriented programming, the underlying principle of using a single interface for multiple data types can be applied in other programming paradigms as well. For example, function overloading (in languages that support it) can be considered a form of polymorphism.