Python polymorphism is the principle that lets you write a single piece of code that works with objects of different types, as long as those objects support the operations the code requires. The word comes from Greek roots meaning "many forms," and in programming it describes the ability of different classes to be treated through a shared interface. If you write a function that calls a speak method on whatever object it receives, that function will work with a Dog class, a Cat class, a Person class, or any other class that defines a speak method. The function does not need to know which specific type of object it is dealing with; it only needs to know that the object can speak. This decoupling of code from concrete types is what makes large object-oriented programs manageable: you can add new classes that support the same interface without changing any of the existing code that uses that interface.
Polymorphism in Python is closely tied to the language's dynamic type system and to the concept of duck typing, which is the subject of the next article in this section. Where languages like Java and C++ require you to declare that a class implements a specific interface or inherits from a specific base class before it can be used polymorphically, Python simply checks whether the object has the required method at the moment the method is called. If the method exists, the call succeeds. If it does not, Python raises an AttributeError. This approach is sometimes called structural subtyping: the type of an object is determined by its structure, meaning the methods and attributes it provides, rather than by its declared place in a class hierarchy.
The article on method overriding in Python covered how child classes can replace parent methods with their own implementations. Polymorphism is the reason that overriding is useful: it lets different classes provide different behaviors under the same method name, and code that uses those classes through the shared method name works with all of them without modification.
Polymorphism through inheritance
The most structured form of polymorphism in Python uses a shared parent class that defines a common interface, and child classes that override the parent's methods to provide their own implementations. Code that works with the parent type can accept any child type, and method calls are dispatched to the appropriate child implementation automatically. This is the classical object-oriented form of polymorphism, and it works in Python exactly as it works in languages with stricter type systems.
Consider a parent class that defines a common interface for processing a payment, and two child classes that implement that interface for different payment methods:
class PaymentMethod:
def process(self, amount):
raise NotImplementedError("Subclass must implement process")
class CreditCard(PaymentMethod):
def process(self, amount):
return f"Charged ${amount:.2f} to credit card"
class PayPal(PaymentMethod):
def process(self, amount):
return f"Sent ${amount:.2f} via PayPal"The parent class defines the process method and raises NotImplementedError as a signal that child classes are expected to override it. This pattern creates a clear contract: any class that inherits from PaymentMethod must provide a process method. The child classes each provide their own implementation, returning different confirmation messages for different payment types.
Now a function that accepts any PaymentMethod can process a payment without knowing which specific payment method is being used:
def checkout(payment_method, amount):
result = payment_method.process(amount)
print(result)
return resultThe checkout function calls process on whatever payment_method object it receives. If the object is a CreditCard, the credit card processing logic runs. If it is a PayPal, the PayPal logic runs. The function does not check the type of the object, does not use isinstance, and does not have separate branches for different payment types. It relies entirely on polymorphism: different objects, same method name, different behaviors, one unified calling code.
Polymorphism without inheritance
Python's dynamic type system means you do not need a shared parent class to use objects polymorphically. Any two classes that define methods with the same names can be used interchangeably in code that calls those methods. This is the essence of duck typing, and it makes polymorphism in Python more flexible and less ceremonious than in languages that require explicit interface declarations.
The following two classes have no shared parent, no declared interface, and no inheritance relationship at all. They simply both define a describe method:
class Book:
def describe(self):
return f"A {self.genre} novel titled '{self.title}'"
class Film:
def describe(self):
return f"A {self.genre} film titled '{self.title}'"A function that prints descriptions of items in a catalog does not care whether each item is a Book or a Film. It only cares that each item has a describe method that returns a string:
def print_catalog(items):
for item in items:
print(item.describe())You can mix Book and Film objects in the same list, pass the list to print_catalog, and each object's describe method runs correctly. The function has no knowledge of the Book or Film classes and no dependency on either one. If you later add an Album class that also defines describe, the same print_catalog function works with Album objects without any changes. This is polymorphism in its most Pythonic form: code that depends on what objects can do, not on what they are.
Polymorphism and the open-closed principle
Polymorphism directly supports the open-closed principle, one of the foundational ideas in software design. The principle states that code should be open for extension but closed for modification. In practice, this means you should be able to add new functionality by writing new classes rather than by changing existing functions. Polymorphism makes this possible because existing functions that work with a shared interface automatically work with new classes that implement that interface.
The print_catalog function from the previous example is closed for modification: you do not need to edit it to support new types of catalog items. It is open for extension: you can add a Podcast class, a Game class, or any other class that defines describe, and the existing function handles them without changes. This property is what makes large codebases maintainable over time. The core logic stays stable while the set of types it operates on grows.
The article on inheritance in Python explained covers the is-a relationship that makes inheritance-based polymorphism natural. The article on duck typing, which follows this one, covers Python's approach to polymorphism without inheritance in more detail, including how to handle cases where an object might or might not support a required operation.
Rune AI
Key Insights
- Polymorphism means writing code that works with objects of different types through a shared interface of method names.
- Python achieves polymorphism through duck typing: if an object has the required method, it can be used, regardless of its class.
- Inheritance is one way to share a common interface, but Python does not require inheritance for polymorphic code.
- Polymorphic functions accept any object that supports the needed operations, making code more flexible and reusable.
- The combination of polymorphism and duck typing is one of Python's most distinctive and powerful features.
Frequently Asked Questions
What is polymorphism in Python?
How is polymorphism different from inheritance?
Does Python support method overloading like Java or C++?
Conclusion
Polymorphism is what makes object-oriented code flexible. When your functions and methods work with objects based on what those objects can do rather than what class they belong to, your code becomes simpler, more reusable, and easier to extend. Python's dynamic type system makes polymorphism natural and idiomatic: you do not need to declare interfaces or write type hierarchies to benefit from it. Any set of classes that share method names can be used polymorphically.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.