These two features are what make classes genuinely powerful once you have more than one related class.
Inheritance lets one class build on another, reusing its properties and methods instead of rewriting them — Python's syntax for this is putting the parent class name in parentheses:
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hi, I'm {self.name}."
class Student(Person):
def __init__(self, name, course):
super().__init__(name) # reuse Person's constructor
self.course = course
s = Student("Amit", "Graphic Design")
print(s.greet()) # "Hi, I'm Amit." — inherited from Person, unchangedsuper() gives access to the parent class's own version of a method, most commonly used to reuse its __init__.
A subclass can also replace a parent's method entirely by defining one with the same name — no special keyword needed:
class Student(Person):
def __init__(self, name, course):
super().__init__(name)
self.course = course
def greet(self): # overrides Person.greet completely
return f"Hi, I'm {self.name}, studying {self.course}."
s = Student("Amit", "Graphic Design")
print(s.greet()) # "Hi, I'm Amit, studying Graphic Design." — Student's version runs, not Person'ssuper().greet() would still be available inside the override if you wanted to build on the parent's version rather than fully replace it — the same super() used above for __init__.
Polymorphism means different classes can implement the same method name in their own way, and calling code doesn't need to know which exact class it's working with:
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
for animal in [Dog(), Cat()]:
print(animal.speak()) # "Woof!" then "Meow!" — same call, different behaviorNotice Dog and Cat here aren't related by inheritance at all — Python doesn't require a shared parent class or interface for this to work, and doesn't check ahead of time that a method exists before calling it. This looser style is often called duck typing: "if it walks like a duck and quacks like a duck" — if an object has a .speak() method, Python is happy to call it, regardless of the object's actual class.
print(isinstance(s, Student)) # True
print(isinstance(s, Person)) # True — Student is also a Person, via inheritance