Bundle state + behaviour into a custom type that participates in Python’s polymorphic dispatch.
A class is a blueprint; an instance is a house; inheritance is a derived blueprint with modifications.
class Animal: class Dog(Animal):
def speak(self): overrides:
return 'generic' def speak(self):
return 'woof'
Animal()=='generic'; Dog()=='woof'
MRO: Dog -> Animal -> object
Instance attrs live in self.__dict__; class attrs are SHARED across instances. Inheritance uses the C3 linearisation to compute MRO, so diamond inheritance is predictable. Composition (‘has-a’) is generally preferred over inheritance (‘is-a’); use inheritance only for true Liskov substitution. super() chains parent calls; abc.ABC declares interface contracts.
class Counter:
def __init__(self, start=0):
self._n = start
def inc(self, by=1):
self._n += by
c = Counter(); c.inc(); c.inc(5)
canonical class; _n signals ‘intended private by convention’.
class Dog(Animal):
def speak(self): return 'woof'
print(Dog.__mro__) # (Dog, Animal, object)
subclass overrides; mro shows resolution order.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Square(Shape):
def __init__(self, s): self.s = s
def area(self): return self.s * self.s
ABC forbids Shape() instantiating; Square must implement area.
Overriding __init__ and forgetting to call super().__init__(...) - parent state never init’d; AttributeError later. Avoid deep inheritance chains; prefer composition.
Favour composition; inheritance only for true IS-A; always super().__init__(); ABC for interface contracts; remember class attrs are shared.
Cache instances shared the same dict; cache key collisions across users.class Cache: store = {} (class attr, mutable!) and never assigned self.store in init.self.store = {} in init; restart workers.