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() is Python's equivalent of PHP's parent:: — it gives access to the parent class's own version of a method, most commonly used to reuse its __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, unlike PHP, which needs an explicit interface to guarantee 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