Object-Oriented Programming — From Classes to Protocols
Part 1: Classes from Scratch
The Class Statement and __init__
A class is a blueprint for objects. When you call MyClass(), Python allocates a new instance (__new__), then calls __init__ to initialize it. self is not a keyword — it is simply the first parameter of every instance method, and Python automatically passes the instance as that first argument.
Instance vs Class Attributes
classmethod and staticmethod
Part 2: Dunder (Magic) Methods
__repr__ and __str__
Comparison Operators and @total_ordering
Arithmetic Operators and Reflected Methods
Container Protocol
Context Manager, __call__, __bool__, and __slots__
Part 3: Inheritance and MRO
Single Inheritance and super()
Method Resolution Order (MRO) — C3 Linearization
Python uses the C3 linearization algorithm to compute the MRO. The invariant: a class always appears before its parents, and if two classes share an ancestor, the one listed first in the inheritance list takes precedence. This guarantees the ancestor is called exactly once in the diamond problem.
Part 4: Properties and Descriptors
Properties — Controlled Attribute Access
Descriptors — The Engine Behind Properties
Any class defining __get__, __set__, or __delete__ is a descriptor. Properties, classmethods, and staticmethods are all descriptors under the hood. Writing your own descriptor lets you reuse validation logic across multiple classes.
Part 5: Abstract Classes and Protocols
ABC — Enforced Interfaces
typing.Protocol — Structural Subtyping
Part 6: Dataclasses
@dataclass — Auto-generated Boilerplate
__post_init__, frozen, and inheritance
Part 7: Advanced OOP Patterns
Metaclasses and __init_subclass__
Observer Pattern
Project: Bank Account System
Exercises
Easy
1. Immutable Point3D
2. StringBuilder
Medium
3. Cached Property Descriptor
4. Type-checked Model
Hard
5. Decorator-based Event Handling
6. Unit Conversion Descriptors
7. Composable Pipeline
8. Abstract Shape Registry