cs.thefarshad
medium

Object-Oriented Python

Classes, __init__, instances and methods, inheritance, dunder methods, and the method resolution order.

A class is a blueprint that bundles data (attributes) with behavior (methods). An instance is a concrete object built from that blueprint. Python’s object model is small but consistent: almost everything — even an int or a function — is an object with a class.

class Dog:
    species = "Canis familiaris"   # class attribute — shared by all instances

    def __init__(self, name, age): # the initializer, runs on construction
        self.name = name           # instance attributes — unique per object
        self.age = age

    def bark(self):                # a method; `self` is the instance
        return f"{self.name} says woof"

d = Dog("Rex", 3)
print(d.bark())                    # Rex says woof
print(d.species)                  # Canis familiaris

__init__, self, and attributes

__init__ is not a constructor that creates the object — Python already created it and passes it in as self. __init__ only initializes it. Every method receives the instance as its explicit first parameter, by convention named self. Instance attributes (set via self.x = ...) belong to one object; class attributes are defined in the class body and shared by all instances until shadowed.

Inheritance

A subclass inherits the attributes and methods of its base class and can add or override them. Call the parent’s version with super():

class Puppy(Dog):
    def __init__(self, name):
        super().__init__(name, age=0)   # reuse Dog's setup
    def bark(self):                      # override
        return f"{self.name} yips"

p = Puppy("Bit")
print(p.bark())                          # Bit yips
print(isinstance(p, Dog))               # True — a Puppy is-a Dog

Dunder methods

Dunder (“double underscore”) methods let your objects plug into Python’s syntax. Define __repr__ for a debugging string, __eq__ for ==, __len__ for len(), __add__ for +, and so on. This is how Python achieves uniform behavior across built-in and user-defined types.

class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

print(Vector(1, 2) + Vector(3, 4))   # Vector(4, 6)
print(Vector(1, 2) == Vector(1, 2))  # True

Always provide a __repr__ — it makes debugging and logging far easier.

Method Resolution Order

When you access obj.attr, Python checks the instance dictionary first, then walks the class’s MRO — the linearized list of classes to search. With single inheritance this is just the chain up to object. With multiple inheritance Python uses the C3 linearization algorithm to produce one consistent order, which is what makes the diamond problem well-defined. Pick an attribute below and watch the lookup walk D -> B -> C -> A -> object, stopping at the first class that defines it.

look up d.
class hierarchy
A
greet, name
inherits
B
greet
C
ping
D
no attrs
d = D() · instance __dict__: {}
D.__mro__ (C3 linearization)
instance __dict__ {}
0D
1Bgreet
2Cping
3Agreet, name
4object__init__
1/3
d.greet — first check the instance __dict__ (empty here)
class A:
    def greet(self): return "A"
class B(A):
    def greet(self): return "B"
class C(A):
    def ping(self):  return "C"
class D(B, C):
    pass

print(D.__mro__)        # (D, B, C, A, object)
print(D().greet())      # 'B' — first match in the MRO
print(D().ping())       # 'C' — inherited from C

You can always inspect the order with D.__mro__ or D.mro().

Takeaways

  • A class bundles attributes and methods; instances are built from it.
  • __init__ initializes the already-created object passed in as self.
  • Class attributes are shared; instance attributes (self.x) are per-object.
  • Subclasses inherit and override; use super() to extend the parent.
  • Dunder methods integrate objects with Python’s operators and built-ins.
  • Attribute lookup follows the MRO, computed by C3 linearization for multiple inheritance.

References