Classes and Inheritance
Class Definition
Section titled “Class Definition”In Python, a class is created with the class keyword. A class is itself an object — an instance of type. The body of a class statement executes at definition time (when the module is imported or the function containing it is called), and the resulting namespace dictionary becomes the class”s __dict__.
class Point: """A simple 2D point."""
def __init__(self, x, y): self.x = x self.y = y
def distance_to(self, other): return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5The class statement does three things:
- Creates a new namespace (a dictionary) for the class body.
- Executes every top-level statement in that namespace — assignments create class attributes,
defstatements create class methods, and even arbitrary expressions are evaluated. - Calls
type(name, bases, namespace)to construct the class object, binding it to the class name in the enclosing scope.
class Tracer: print("Class body is executing right now")
def method(self): pass
## Output when the module loads: "Class body is executing right now"This means class bodies are not inert declarations. They are executable code. This property is the foundation of metaclasses, class decorators, and many advanced patterns.
__init__ and self
Section titled “__init__ and self”__init__ is the initializer, not the constructor. The actual constructor is __new__A class Method on type that allocates the instance. __init__ receives the already-allocated instance and Populates it.
class Demo: def __new__(cls, *args, **kwargs): print(f"__new__ called on {cls}") instance = super().__new__(cls) return instance
def __init__(self, value): print(f"__init__ called on {self}") self.value = valueWhy self Instead of Implicit this
Section titled “Why self Instead of Implicit this”Python requires the instance to be passed explicitly as the first parameter of instance methods. The Parameter is conventionally named selfThough the language does not enforce this name — any Valid identifier works.
This is a deliberate design choice with several consequences:
Explicit is better than implicit. If
selfwere implicit, a method’s free variables would include an implicitly-bound name that shadows any outer variable with the same name. By makingselfan explicit parameter, the binding is always visible at the call site (even if the caller does not write it — the interpreter inserts it automatically when using dotted access).Methods are just functions. A method and a standalone function share the exact same calling convention. The only difference is that
obj.method(args)is syntactic sugar fortype(obj).method(obj, args). This means you can pass methods as first-class objects, assign functions to class attributes to turn them into methods, and unbind methods from instances — all without any special machinery.Uniformity with
cls. Class methods explicitly receive the class as their first parameter. Static methods receive nothing. All three cases follow the same rule: the first parameter is whatever the descriptor protocol provides. An implicitthiswould require a special case for every binding type.
def standalone_func(self, x, y): return x + y
class Hijack: method = standalone_func
h = Hijack()print(h.method(1, 2)) # 3 -- a plain function becomes a bound methodInstance Variables vs Class Variables
Section titled “Instance Variables vs Class Variables”Instance variables are stored in each object’s __dict__ and are set inside methods ( __init__). Class variables are stored in the class’s __dict__ and are shared across all Instances.
class Dog: species = "Canis familiaris"
def __init__(self, name): self.name = name
a = Dog("Rex")b = Dog("Buster")
print(Dog.species) # Canis familiarisprint(a.species) # Canis familiaris (found on class)print(a.name) # Rex (found on instance)Attribute lookup follows the chain: instance __dict__ then class __dict__ then base classes (following the MRO). Assignment to an attribute through an instance always sets it on the Instance, never on the class.
a.species = "Wolf"print(a.species) # Wolf (instance dict)print(b.species) # Canis familiaris (class dict)print(Dog.species) # Canis familiaris (unchanged)Cross-References
Section titled “Cross-References”- Metaclasses: Extends class creation by customizing the class creation process itself, building on the class fundamentals covered here.
- Descriptors: Explains the underlying mechanism that makes properties, class methods, and static methods work.
- Protocols and Dunder Methods: Provides deeper coverage of the dunder methods that enable Python’s data model integration.
- Data Validation: Shows how to use dataclasses and validators to ensure data integrity in object-oriented designs.