Study guide
6.1 Single Inheritance, Method Overriding, and Polymorphism
OpenEDG PCAP — Certified Associate in Python Programming Exam
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: The PCAP-31-03 Exam Blueprint and Test-Day Logistics
1.1 The PCAP-31-03 Blueprint, Scoring, and Exam-Day Logistics
2 Chapter 2: Modules, Packages, and the Standard Library
2.1 Module Imports and Namespace Mechanics 2.2 Module Construction and Execution 2.3 Package Hierarchy and Navigation 2.4 Standard Library Modules and Pip
3 Chapter 3: Exceptions and Error Handling
3.1 Exception Control Flow and Syntax 3.2 The Built-in Exception Hierarchy 3.3 Raising Exceptions and Assertions 3.4 Custom Exceptions and Exception Chaining
4 Chapter 4: Strings, Encodings, and Text Processing
4.1 Character Encodings and String Immutability 4.2 String Indexing, Slicing, and Comparison 4.3 String Inspection and Classification Methods 4.4 String Transformation and Formatting Methods 4.5 Iterating, Sorting, and Comparing Strings Against Other Types
5 Chapter 5: Object-Oriented Programming Fundamentals
5.1 Classes, Instances, and Attributes 5.2 Encapsulation, Privacy, and Name Mangling 5.3 Dunder Methods and Object Lifecycle 5.4 Discovering Class Structure: __name__, __module__, __bases__, and Identity
6 Chapter 6: Advanced OOP, Inheritance, and Metaprogramming
6.1 Single Inheritance, Method Overriding, and Polymorphism 6.2 Multiple Inheritance and Method Resolution Order (MRO) 6.3 Class Methods, Static Methods, and Properties
7 Chapter 7: Functional Python, Comprehensions, and Closures
7.1 List, Dictionary, and Set Comprehensions 7.2 Lambda Expressions and Higher-Order Functions 7.3 Variable Scopes, the LEGB Rule, and Closures
8 Chapter 8: Iterators, Generators, and File I/O
8.1 Iterators and the Iteration Protocol 8.2 Generator Functions and Generator Expressions 8.3 File Input/Output and Context Managers 8.4 Predefined Streams, Handles vs. Streams, and errno Diagnostics
6.1 Single Inheritance, Method Overriding, and Polymorphism
Key Takeaways Explain This Topic AI
Inheritance establishes an 'is-a' relationship where subclasses inherit accessible attributes and methods from superclasses to enable hierarchical code reuse and specialization.
Method overriding occurs when a subclass defines a method with the identical identifier as a superclass method, replacing or extending parent behavior during dynamic runtime dispatch.
The zero-argument super() built-in in Python 3 proxies method calls to the parent class, ensuring proper superclass attribute initialization via super(). init (...).
The isinstance() function tests instance membership across inheritance trees (supporting class tuples), while issubclass() tests class-level derivation (where every class is a subclass of itself and object).
Polymorphism in Python relies on duck typing ('if it walks and quacks like a duck, it is a duck'), enabling uniform operational interfaces without nominal type restrictions.
Last updated: August 2026
Single Inheritance, Method Overriding, and Polymorphism
Object-Oriented Programming (OOP) in Python is built upon the foundational pillars of encapsulation, abstraction, inheritance, and polymorphism. While basic OOP focuses on modeling entities using classes and instances, advanced OOP leverages inheritance hierarchies to establish relationships between concepts, reduce code duplication, and construct extensible software architectures.
Understanding how Python handles class derivation, method resolution, superclass delegation via super() , runtime type introspection, and dynamic polymorphism is essential for mastering Python and excelling on the OpenEDG PCAP-31-03 certification exam.
1. Concepts and Terminology of Inheritance
Inheritance is a mechanism where a new class (known as a subclass , derived class , or child class ) is created based on an existing class (known as a superclass , base class , or parent class ).
Inheritance models an "is-a" relationship . For example:
A Car is-a Vehicle .
A Manager is-a Employee .
A SecureSocket is-a Socket .
Syntax for Class Derivation
In Python, inheritance is declared by placing the name of the superclass in parentheses immediately following the subclass name in the class definition header: class Superclass:
- """Parent base class."""
- pass
class Subclass(Superclass):
- """Child derived class inheriting from Superclass."""
- pass
- Python's Universal Root: The object Class
In Python 3, all classes implicitly inherit from the built-in object base class if no explicit superclass is specified. The following two declarations are semantically identical: # Implicit inheritance from object
class Appliance:
pass
# Explicit inheritance from object (redundant in Python 3)
class Appliance(object):
pass
The object class supplies standard dunder methods (such as __str__() , __repr__() , __eq__() , __hash__() , and __init__() ) to every Python object.
Inheriting Attributes and Methods
When a subclass is instantiated, it automatically inherits all class attributes, instance methods, and behaviors defined on its superclass hierarchy without requiring manual redefinition: class Vehicle:
category = "Land Transportation"
def __init__(self, brand, model):
- self.brand = brand
- self.model = model
def describe(self):
return f"{self.brand} {self.model} ({self.category})"
class Car(Vehicle):
- # Car inherits category, __init__, and describe() automatically
- pass
- sedan = Car("Toyota", "Camry")
- print(sedan.describe()) # Output: Toyota Camry (Land Transportation)
- print(sedan.category) # Output: Land Transportation
- 2. Method Overriding
Method overriding occurs when a subclass defines a method with the exact same identifier (name) and parameter signature as a method present in its superclass. Overriding allows a subclass to provide a specialized implementation tailored to its specific requirements while preserving a uniform interface.
Dynamic Dispatch Mechanics
When a method is called on an instance (e.g., instance.method() ), Python's attribute lookup inspects the instance's class first. If the method is defined directly in that class, Python executes it immediately, effectively shadowing (hiding) any identically named method in the parent class: class Employee:
def __init__(self, name, base_salary):
- self.name = name
- self.base_salary = base_salary
def calculate_bonus(self):
- # Standard employee bonus: 5% of base salary
- return self.base_salary * 0.05
class Salesperson(Employee):
def __init__(self, name, base_salary, commission):
- # We will explore super().__init__ shortly
- self.name = name
- self.base_salary = base_salary
- self.commission = commission
def calculate_bonus(self):
- # Method Overridden: Sales bonus includes 10% base + direct commission
- return (self.base_salary * 0.10) + self.commission
- emp = Employee("Alice", 60000)
- seller = Salesperson("Bob", 60000, 5000)
- print(emp.calculate_bonus()) # Output: 3000.0 (Standard 5% bonus)
- print(seller.calculate_bonus()) # Output: 11000.0 (Overridden bonus calculation)
- 3. Superclass Delegation with super()
Often, an overriding subclass method should not completely replace superclass functionality, but rather extend it by executing parent logic and adding custom behavior. Hardcoding the parent class name (e.g., Employee.calculate_bonus(self) ) introduces rigid coupling and breaks under complex inheritance hierarchies.
Python provides the built-in super() function as a dynamic proxy that delegates method calls to the appropriate superclass.
Zero-Argument super() in Python 3
In Python 3, super() is called inside method definitions with zero arguments. The compiler automatically supplies the current enclosing class and instance ( self ): super().method(args) ≡ super(CurrentClass, self).method(args) \text{super().method(args)} \equiv \text{super(CurrentClass, self).method(args)} super().method(args) ≡ super(CurrentClass, self).method(args)
Chaining Constructors: super().__init__(...)
The most common application of super() is invoking the parent class's constructor inside the subclass __init__() method: class Device:
def __init__(self, serial_number, power_rating):
- self.serial_number = serial_number
- self.power_rating = power_rating
- self.is_powered = False
def power_on(self):
self.is_powered = True
class Laptop(Device):
def __init__(self, serial_number, power_rating, battery_capacity, os_name):
- # Delegate common initialization to Device.__init__
- super().__init__(serial_number, power_rating)
- # Initialize subclass-specific attributes
- self.battery_capacity = battery_capacity
- self.os_name = os_name
def power_on(self):
- # Extend Device.power_on with operating system boot message
- super().power_on()
- return f"Laptop {self.serial_number} booted into {self.os_name}."
- macbook = Laptop("MBP-2026-X", 67, 70, "macOS")
- print(macbook.is_powered) # Output: False (Initialized by Device.__init__)
print(macbook.power_on()) # Output: Laptop MBP-2026-X booted into macOS.
print(macbook.is_powered) # Output: True (Updated by Device.power_on via super())
Warning
The Missing super().__init__() Bug: If a subclass defines its own __init__() method and fails to call super().__init__() , the superclass's __init__() method is never executed . Consequently, instance attributes expected from the parent class will not be initialized, causing runtime AttributeError exceptions when accessed. class Parent:
def __init__(self):
self.parent_attr = "initialized"
class Child(Parent):
def __init__(self):
- # Omitting super().__init__()
- self.child_attr = "ready"
- c = Child()
- print(c.child_attr) # Output: ready
try:
print(c.parent_attr)
except AttributeError as err:
- print("Caught error:", err) # Caught error: 'Child' object has no attribute 'parent_attr'
- 4. Runtime Type Inspection: isinstance() and issubclass()
Python provides two primary introspection built-in functions to verify class relationships and object types at runtime.
The isinstance(object, classinfo) Function
The isinstance() built-in checks whether an object instance is an instance of a given class or any subclass derived from it: class Animal: pass
- class Dog(Animal): pass
- class Labrador(Dog): pass
my_pet = Labrador()
- print(isinstance(my_pet, Labrador)) # True (Direct class instance)
- print(isinstance(my_pet, Dog)) # True (Inherited from Dog)
- print(isinstance(my_pet, Animal)) # True (Inherited from Animal)
- print(isinstance(my_pet, object)) # True (All objects subclass object)
- print(isinstance(my_pet, str)) # False (Unrelated type)
- Tuple of Classes in isinstance()
classinfo can be passed as a tuple of types. isinstance() returns True if the object is an instance of any type in the tuple (logical OR): val = 42
- print(isinstance(val, (str, list, tuple))) # False
- print(isinstance(val, (str, int, float))) # True (Matches int)
- The issubclass(class, classinfo) Function
The issubclass() built-in inspects relationships between class objects (types), rather than instances: print(issubclass(Labrador, Dog)) # True (Labrador derives from Dog)
- print(issubclass(Labrador, Animal)) # True (Labrador derives indirectly from Animal)
- print(issubclass(Dog, Labrador)) # False (Superclass is not a subclass of Child)
- Fundamental Rules for issubclass() on the PCAP Exam
Reflexivity: A class is always considered a subclass of itself: issubclass ( C , C ) = = True \text{issubclass}(C, C) == \text{True} issubclass ( C , C ) == True
Universal Derivation: Every class in Python 3 is a subclass of object : issubclass ( C , object ) = = True \text{issubclass}(C, \text{object}) == \text{True} issubclass ( C , object ) == True
Type Constraint: Both arguments must be class types. Passing an instance as the first argument raises a TypeError : pet = Dog()
try:
issubclass(pet, Animal) # 'pet' is an instance, NOT a class!
except TypeError as err:
print("TypeError:", err) # issubclass() arg 1 must be a class
Tuple Support: Like isinstance() , issubclass() accepts a tuple of target candidate classes.
Comparison Table: Type Introspection Mechanisms
- Mechanism First Argument Considers Subclasses? Best Use Case
- type(obj) is Cls Instance No (Strict exact identity) Exact type pinning without inheritance
- isinstance(obj, Cls) Instance Yes (Polymorphic membership) Idiomatic runtime type checking
- issubclass(Sub, Cls) Class Object Yes (Class tree hierarchy) Verifying API contract / class hierarchies
- 5. Polymorphism and Duck Typing
Polymorphism (from Greek: "having multiple forms") refers to the programming capability where different classes can define methods sharing the identical interface (method name and parameter signature), allowing client code to treat instances of different classes uniformly without knowing their specific underlying types.
Nominal Polymorphism vs. Duck Typing
In statically typed languages (such as C++ or Java), polymorphism requires explicit nominal inheritance: all participating classes must inherit from a common abstract base class or implement a shared interface.
In Python, polymorphism is governed by Duck Typing :
"If it walks like a duck and quacks like a duck, it's a duck."
Duck typing prioritizes what an object can do (its methods and attributes) rather than what it is (its nominal class inheritance): class PDFExporter:
def export(self, payload):
return f"Rendering PDF document with payload: {payload}"
class CSVExporter:
def export(self, payload):
return f"Writing CSV file with payload: {payload}"
class CloudSyncExporter:
def export(self, payload):
return f"Pushing JSON payload to cloud endpoint: {payload}"
# Polymorphic consumer function
def publish_report(exporter, data):
- # The function only cares that 'exporter' has a callable 'export' method
- result = exporter.export(data)
- print("Status:", result)
- # All three distinct types operate seamlessly without a shared base class
- publish_report(PDFExporter(), {"total": 500})
- publish_report(CSVExporter(), {"total": 500})
- publish_report(CloudSyncExporter(), {"total": 500})
- Dynamic Method Resolution at Runtime
Because Python resolves attribute and method lookups dynamically at the exact moment of execution (runtime dispatch), new classes can be introduced to an existing system at any time without modifying downstream consumers, providing immense architectural flexibility.