Polymorphism and Method Resolution
A base class defines shared attributes and methods; derived classes inherit from it and add their own specialized features.
Try it: Inheritance and Method Lookup
How a subclass inherits from its parent, how Python finds an attribute by walking the instance, then the class's method resolution order up to object, how overriding and super() work, why the same call does different things on different objects (polymorphism), and what isinstance / issubclass check.
How it works
- class Dog(Animal) gives Dog the MRO Dog → Animal → object; Puppy(Dog) gives Puppy → Dog → Animal → object.
- obj.name searches obj.__dict__, then each class of type(obj).__mro__ in order; the first class that defines the name wins, so a subclass method overrides the parent's.
- super().method() starts the search in the class after the one the current method was defined in, e.g. to run the parent's __init__.
- Calling the same method on a list of different objects runs each object's own version (polymorphism).
- isinstance(obj, C) is True when C is in type(obj).__mro__; if no class has the name, Python raises AttributeError.
Default run (63 steps): Ready: Animal, Dog, Cat and Puppy, then the statements you picked. Each attribute is searched along the class's MRO. … Finished. Printed: Rex the dog says Woof / Tom the cat says Meow / Bo the dog says Woof! / True / Puppy / Dog / Animal / object.
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Single inheritance only (the lab's MRO code is Python's C3 algorithm, but no class here has two parents).
Loading the simulation…
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.
- 1Create the base class
SchoolMember holds the attributes and method common to all school members: name, age, and
tell(). - 2Create the teacher subclass
Teacher inherits name, age, and
tell()from SchoolMember, then adds salary and teacher-specific behavior. - 3Create the student subclass
Student inherits name, age, and
tell()from SchoolMember, then adds marks and student-specific behavior. - 4Use 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
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.
SchoolMember.tell(self)The inherited method supplies the shared name and age details.
SchoolMember.tell(self)
print salary or marksThe derived method keeps the shared details and adds 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 |
The subclasses reuse common features while adding or customizing behavior.
Mistakes with Inheritance
Common mistake Skipping the parent __init__ call
What it looks likeA derived constructor initializes salary or marks but never calls SchoolMember.__init__(self, name, age).
Why it failsThe inherited name and age attributes are not initialized by the base class.
Do this insteadCall the parent initializer before adding the derived class's attributes.
Common mistake Assuming the base tell() method always runs
What it looks likeA Teacher object defines tell(), but the programmer expects SchoolMember.tell() to be selected automatically.
Why it failsThe derived method overrides the inherited method for that derived object.
Do this insteadCall SchoolMember.tell(self) inside the overriding method when the shared behavior is needed.
Common mistake Duplicating shared attributes in every subclass
What it looks likeTeacher and Student each define their own copies of name and age instead of inheriting them.
Why it failsThis repeats functionality that belongs in the common base class and makes maintenance harder.
Do this insteadDefine shared attributes and methods once in SchoolMember and inherit them.
Common mistake Checking every concrete type before calling tell()
What it looks likeA loop branches separately for Teacher and Student instead of calling member.tell().
Why it failsThe code gives up the flexibility provided by polymorphism.
Do this insteadUse 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.
- 1First iteration
The current object is a Teacher, so the call resolves to
Teacher.tell(). - 2Reuse shared behavior
Teacher.tell()callsSchoolMember.tell(self), displaying the teacher's name and age. - 3Add teacher behavior
Teacher.tell()then displays the teacher's salary. - 4Second iteration
The current object is a Student, so the call resolves to
Student.tell(). - 5Add 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
- 1A 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, usingParentClassName.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.
At a glance
- 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.