Polymorphism and Method Resolution
A base class defines shared attributes and methods; derived classes inherit from it and add their own specialized features.
One Interface, Different Results
Suppose a program stores both teachers and students as school members. The program can ask every member to tell() about themselves without first checking whether the member is a teacher or a student. The same method call can produce teacher-specific or student-specific behavior because each derived class provides its own version of tell(). That combination of shared structure and specialized behavior is the central idea behind inheritance, method resolution, and polymorphism.
Base and Derived Classes
A base class, also called a superclass, contains attributes and methods shared by a group of related objects. A derived class, also called a subclass, inherits those attributes and methods and can add specialized features of its own. Inheritance lets you define common functionality once instead of repeating the same attributes and methods in every related class.
Separating Shared and Specialized Data
Model teachers and students without defining name and age separately in both classes.
Create the base class: SchoolMember holds the attributes and method common to all school members: name, age, and tell().
Create the teacher subclass: Teacher inherits name, age, and tell() from SchoolMember, then adds salary and teacher-specific behavior.
Create the student subclass: Student inherits name, age, and tell() from SchoolMember, then adds marks and student-specific behavior.
Use the inherited features: A Teacher object and a Student object both have name and age because those attributes come from SchoolMember.
The base class contains shared functionality, while each derived class adds only the features that distinguish it.
Initialization Order
When a derived class defines its own __init__ method, it should call the parent class __init__ method to initialize the inherited attributes. In the school example, creating a Teacher first enters Teacher.__init__. That method explicitly calls SchoolMember.__init__(self, name, age), which initializes name and age. After the parent initialization completes, Teacher.__init__ adds salary.
What do you think happens?
When a Teacher object is created, which initialization message appears first: the SchoolMember message or the Teacher message?
Reveal answer
Answer: SchoolMember first, then Teacher
Teacher.__init__ explicitly calls SchoolMember.__init__ before it completes the Teacher-specific initialization. The base initialization therefore runs before the derived class finishes.
Overriding an Inherited Method
Method overriding occurs when a derived class defines a method with the same name as a method in its base class. The derived version replaces the inherited version for objects of that derived class. A subclass can still reuse the parent's implementation by calling ParentClassName.method_name(self). In the school example, Teacher.tell() and Student.tell() each call SchoolMember.tell(self) first and then add specialized information.
When overriding a method, call the parent version when the shared behavior is still needed. This allows the derived method to extend the inherited behavior instead of repeating the base method's implementation.
Polymorphic Method Calls
Polymorphism allows code to use the same method call with objects from different derived classes. A loop over Teacher and Student objects can call member.tell() for every object without checking its specific class. Python resolves the call to Teacher.tell() for a teacher and Student.tell() for a student. The caller uses one shared method name, while each object supplies its appropriate behavior.
In the school model, the loop does not need separate branches such as “if this is a Teacher” and “if this is a Student.” It simply calls tell() on each school member. Adding another derived class with its own tell() implementation would allow the same style of loop to work with that class as well.
Shared Functionality
| Class | Shared features | Specialized feature | tell() behavior |
|---|---|---|---|
| SchoolMember | name and age | None described | Displays shared member details |
| Teacher | name and age | salary | Displays shared details and salary |
| Student | name and age | marks | Displays shared details and marks |
Mistakes with Inheritance
Skipping the parent __init__ call
The inherited name and age attributes are not initialized by the base class.
Fix:
Call the parent initializer before adding the derived class's attributes.Assuming the base tell() method always runs
The derived method overrides the inherited method for that derived object.
Fix:
Call SchoolMember.tell(self) inside the overriding method when the shared behavior is needed.Duplicating shared attributes in every subclass
This repeats functionality that belongs in the common base class and makes maintenance harder.
Fix:
Define shared attributes and methods once in SchoolMember and inherit them.Checking every concrete type before calling tell()
The code gives up the flexibility provided by polymorphism.
Fix:
Use the shared method call and let Python resolve the appropriate derived implementation.
Practice the Resolution Path
A list contains one Teacher object and one Student object. For each object, trace what happens when a loop calls member.tell(). Identify which tell() implementation runs, which shared details are displayed, and which specialized detail is added.
Hints
- Both objects are school members, so both have name and age.
- Look for the tell() method defined on the object's own derived class first.
- Teacher adds salary, while Student adds marks.
Tracing Two Objects
Determine the behavior of a loop that calls member.tell() for a Teacher followed by a Student.
First iteration: The current object is a Teacher, so the call resolves to Teacher.tell().
Reuse shared behavior: Teacher.tell() calls SchoolMember.tell(self), displaying the teacher's name and age.
Add teacher behavior: Teacher.tell() then displays the teacher's salary.
Second iteration: The current object is a Student, so the call resolves to Student.tell().
Add student behavior: Student.tell() uses the shared school-member behavior and then displays the student's marks.
The loop uses one call, member.tell(), but the selected implementation and specialized output depend on the current object's derived class.
Key Takeaways
- A base class defines functionality shared by related objects, while derived classes inherit that functionality and add specialized features. A derived initializer should call the parent __init__ method so inherited attributes are initialized. A derived class can override a method to replace or extend inherited behavior, using ParentClassName.method_name(self) to call the parent version. Method resolution selects the derived implementation when it exists. Polymorphism lets one method call work with multiple derived classes while producing behavior appropriate to each object.
Key Takeaways
- Base classes define shared attributes and methods; derived classes inherit and specialize them.
- A derived class should call the parent __init__ method to initialize inherited attributes.
- Overriding lets a subclass replace or extend an inherited method.
- Calling the parent method from an override preserves shared behavior without duplicating it.
- Polymorphism allows the same method call to select different derived implementations.