Courses/Python/Magic Methods & the Python Data Model
    Back to Course

    Lesson 28 โ€ข Advanced

    Magic Methods & the Python Data Model

    Master Python's powerful magic methods (dunder methods) and understand how the Python data model works under the hood. Learn to create custom classes that behave like built-in types, implement operator overloading, and build professional-grade objects.

    ๐Ÿ”ฅ 1. What Are Magic Methods?

    Magic methods are methods Python calls automatically when certain operations happen.

    Examples:

    OperationMagic Method
    len(obj)__len__
    obj + other__add__
    obj[i]__getitem__
    for x in obj__iter__
    str(obj)__str__
    with obj:__enter__, __exit__
    obj()__call__

    They let you define how your objects behave in every situation.

    โš™๏ธ 2. Object Construction (Creation & Initialization)

    __new__ โ€” create object

    __init__ โ€” initialize object

    Object Construction

    __new__ and __init__ methods

    Try it Yourself ยป
    Python
    class User:
        def __new__(cls, *args, **kwargs):
            print("Allocating memory...")
            return super().__new__(cls)
    
        def __init__(self, name):
            print("Initializing...")
            self.name = name
    
    # Create a user
    user = User("Alice")
    print(f"User name: {user.name}")

    Use cases:

    • โœ” custom immutable types
    • โœ” singleton patterns
    • โœ” building objects from cached pools

    ๐Ÿงฑ 3. Representation Methods

    These control how an object looks when printed or displayed.

    __repr__ โ€” official representation (for developers)

    __repr__ Method

    Developer-friendly representation

    Try it Yourself ยป
    Python
    class User:
        def __init__(self, name):
            self.name = name
        
        def __repr__(self):
            return f"User(name={self.name!r})"
    
    user = User("Alice")
    print(repr(user))  # User(name='Alice')

    __str__ โ€” user-friendly representation

    __str__ Method

    User-friendly representation

    Try it Yourself ยป
    Python
    class User:
        def __init__(self, name):
            self.name = name
        
        def __str__(self):
            return self.name
    
    user = User("Alice")
    print(str(user))  # Alice
    print(user)       # Alice

    __format__ โ€” formatting rules

    Used for f-strings, formatting currencies, dates, metrics.

    ๐Ÿ”ข 4. Numeric Magic Methods (Act Like Numbers!)

    Implementing these turns objects into custom numeric types:

    Addition

    Numeric Operations

    Vector addition example

    Try it Yourself ยป
    Python
    class Vector:
        def __init__(self, x, y):
            self.x = x
            self.y = y
        
        def __add__(self, other):
            return Vector(self.x + other.x, self.y + other.y)
        
        def __repr__(self):
            return f"Vector({self.x}, {self.y})"
    
    v1 = Vector(1, 2)
    v2 = Vector(3, 4)
    v3 = v1 + v2
    print(v3)  # Vector(4, 6)

    Also available: Subtraction, Multiplication, Division, Modulo, Power, Negation

    Used in:

    • โœ” game engines
    • โœ” simulation math
    • โœ” ML tensor wrappers (PyTorch does this heavily)

    ๐Ÿง  5. Comparisons (Sorting, Ordering, Equality)

    • __eq__ โ€” equals
    • __ne__ โ€” not equal
    • __lt__ โ€” less than
    • __gt__ โ€” greater than
    • __le__ โ€” <=
    • __ge__ โ€” >=

    Example:

    Comparison Methods

    Sorting with __lt__

    Try it Yourself ยป
    Python
    class Player:
        def __init__(self, name, score):
            self.name = name
            self.score = score
        
        def __lt__(self, other):
            return self.score < other.score
        
        def __repr__(self):
            return f"{self.name}: {self.score}"
    
    players = [Player("Alice", 100), Player("Bob", 150), Player("Charlie", 75)]
    sorted_players = sorted(players)
    for p in sorted_players:
        print(p)

    Used for:

    • โœ” leaderboard systems
    • โœ” sorting objects
    • โœ” priority queues
    • โœ” ranking algorithms

    ๐Ÿ“ฆ 6. Container Protocol (Behaving Like Lists & Dicts)

    __len__

    Controls what len(obj) returns.

    __getitem__

    Container Protocol

    List-like behavior

    Try it Yourself ยป
    Python
    class MyList:
        def __init__(self, data):
            self.data = data
        
        def __len__(self):
            return len(self.data)
        
        def __getitem__(self, index):
            return self.data[index]
    
    my_list = MyList([1, 2, 3, 4, 5])
    print(f"Length: {len(my_list)}")
    print(f"First item: {my_list[0]}")
    print(f"Slice: {my_list[1:3]}")

    Enables:

    • โœ” slicing
    • โœ” indexing
    • โœ” iterating

    Also: __setitem__, __delitem__

    ๐Ÿ”„ 7. Iterable Protocol (for...in Loops)

    To make an object iterable:

    • __iter__ โ€” return an iterator
    • __next__ โ€” steps through values

    Iterable Protocol

    Custom iterator

    Try it Yourself ยป
    Python
    class Countdown:
        def __init__(self, start):
            self.start = start
        
        def __iter__(self):
            self._current = self.start
            return self
        
        def __next__(self):
            if self._current <= 0:
                raise StopIteration
            self._current -= 1
            return self._current + 1
    
    for num in Countdown(5):
        print(num)

    Used in:

    • โœ” custom data streams
    • โœ” tokenizers
    • โœ” ML data loaders
    • โœ” scrapers
    • โœ” generators

    ๐Ÿงฉ 8. Callable Objects (Pretend to Be Functions)

    __call__

    Callable Objects

    __call__ method

    Try it Yourself ยป
    Python
    class Adder:
        def __init__(self, n):
            self.n = n
    
        def __call__(self, x):
            return x + self.n
    
    # Now:
    add5 = Adder(5)
    print(add5(10))   # 15
    print(add5(20))   # 25

    Used in:

    • โœ” ML models (PyTorch layers implement __call__)
    • โœ” function wrappers
    • โœ” on-the-fly factories
    • โœ” middleware

    ๐Ÿ” 9. Attribute Access Control

    • __getattr__ โ€” fallback for missing attributes
    • __getattribute__ โ€” intercept ALL attribute access
    • __setattr__ โ€” intercept setting attributes
    • __delattr__ โ€” intercept deletion

    Example:

    Attribute Access

    __getattr__ fallback

    Try it Yourself ยป
    Python
    class FlexibleObject:
        def __init__(self):
            self.data = {}
        
        def __getattr__(self, name):
            return f"{name} is not defined"
    
    obj = FlexibleObject()
    print(obj.name)     # name is not defined
    print(obj.anything) # anything is not defined

    Used for:

    • โœ” lazy loading
    • โœ” proxies (database lazy models in Django)
    • โœ” dynamic API clients
    • โœ” configuration wrappers

    ๐Ÿงฑ 10. Boolean Value

    __bool__

    Boolean Value

    __bool__ method

    Try it Yourself ยป
    Python
    class Container:
        def __init__(self, items):
            self.items = items
        
        def __bool__(self):
            return len(self.items) > 0
    
    empty = Container([])
    full = Container([1, 2, 3])
    
    if full:
        print("Container has items")
    if not empty:
        print("Container is empty")

    Now objects behave logically in:

    • if statements
    • while loops
    • conditional checks

    ๐ŸงŠ 11. Context Managers

    __enter__, __exit__

    Context Manager

    __enter__ and __exit__

    Try it Yourself ยป
    Python
    class Timer:
        def __enter__(self):
            import time
            self.start = time.time()
            print("Starting timer...")
            return self
    
        def __exit__(self, exc_type, exc, tb):
            import time
            elapsed = time.time() - self.start
            print(f"Elapsed: {elapsed:.4f} seconds")
    
    with Timer():
        # Some operation
        total = sum(range(1000000))
        print(f"Sum: {total}")

    Used in:

    • โœ” file handling
    • โœ” database sessions
    • โœ” locking systems
    • โœ” resource guards
    • โœ” timers

    ๐Ÿงฑ 12. Copy & Serialization Hooks

    • __copy__
    • __deepcopy__
    • __getstate__
    • __setstate__

    Control:

    • how objects are serialized
    • custom caching behavior
    • saving/loading ML models
    • multiprocessing transfers

    ๐Ÿ”ฅ 13. Operator Overloading (Make Objects Behave Like Built-ins)

    Python lets you define how your objects react to operators.

    Arithmetic operators

    • __add__(self, other) โ€” +
    • __sub__(self, other) โ€” -
    • __mul__(self, other) โ€” *
    • __truediv__(self, other) โ€” /
    • __floordiv__(self, other) โ€” //
    • __mod__(self, other) โ€” %
    • __pow__(self, other) โ€” **

    Example: Vector math

    Operator Overloading

    Vector math example

    Try it Yourself ยป
    Python
    class Vector:
        def __init__(self, x, y):
            self.x, self.y = x, y
    
        def __add__(self, other):
            return Vector(self.x + other.x, self.y + other.y)
        
        def __sub__(self, other):
            return Vector(self.x - other.x, self.y - other.y)
        
        def __mul__(self, scalar):
            return Vector(self.x * scalar, self.y * scalar)
    
        def __repr__(self):
            return f"Vector({self.x}, {self.y})"
    
    v1 = Vector(3, 4)
    v2 = Vector(1, 2)
    print(f"v1 + v2 = {v1 + v2}")
    print(f"v1 - v2
    ...

    Operator overloading is used everywhere:

    • โœ” game engines
    • โœ” physics simulations
    • โœ” ML tensors (PyTorch / TensorFlow)
    • โœ” financial modeling
    • โœ” vector graphics engines

    ๐Ÿงฑ 14. Rich Comparisons โ€” Smarter Sorting & Ranking

    If you must support ordering, implement the following:

    • __lt__(self, other) โ€” <
    • __le__(self, other) โ€” <=
    • __gt__(self, other) โ€” >
    • __ge__(self, other) โ€” >=
    • __eq__(self, other) โ€” ==
    • __ne__(self, other) โ€” !=

    Example: sortable players by score

    Rich Comparisons

    Sorting and ranking

    Try it Yourself ยป
    Python
    class Player:
        def __init__(self, name, score):
            self.name = name
            self.score = score
        
        def __lt__(self, other):
            return self.score < other.score
        
        def __eq__(self, other):
            return self.score == other.score
        
        def __repr__(self):
            return f"{self.name}({self.score})"
    
    p1 = Player("Alice", 100)
    p2 = Player("Bob", 150)
    print(f"{p1} < {p2}: {p1 < p2}")
    print(f"{p1} == {p2}: {p1 == p2}")

    ๐Ÿงฉ 15. Sequence Protocol (Full Custom List Behavior)

    To behave like a real sequence, implement:

    Required

    • __len__(self)
    • __getitem__(self, index)

    Optional (for mutability)

    • __setitem__(self, index, value)
    • __delitem__(self, index)
    • __contains__(self, item)

    Example: Read-only sequence

    Sequence Protocol

    Read-only sequence

    Try it Yourself ยป
    Python
    class ReadOnlySeq:
        def __init__(self, data):
            self._data = tuple(data)
    
        def __len__(self):
            return len(self._data)
    
        def __getitem__(self, i):
            return self._data[i]
        
        def __contains__(self, item):
            return item in self._data
    
    seq = ReadOnlySeq([1, 2, 3, 4, 5])
    print(f"Length: {len(seq)}")
    print(f"First: {seq[0]}")
    print(f"3 in seq: {3 in seq}")

    ๐Ÿง  16. Iterable vs Iterator (Clear Distinction)

    Iterable

    Has __iter__(), returns an iterator.

    Iterator

    Has __iter__() and __next__().

    Why this matters:

    • for loops depend on it
    • generator pipelines rely on it
    • async systems use async iterators
    • ML dataloaders use custom iterables

    ๐Ÿงฑ 17. Custom Iterators (Full Control of Data Streams)

    Custom Iterator

    Countdown iterator

    Try it Yourself ยป
    Python
    class Countdown:
        def __init__(self, n):
            self.n = n
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.n <= 0:
                raise StopIteration
            self.n -= 1
            return self.n + 1
    
    for num in Countdown(5):
        print(num)

    Used for:

    • โœ” streaming input
    • โœ” chunked database queries
    • โœ” on-the-fly data generation
    • โœ” scraper crawls
    • โœ” batching ML data

    ๐Ÿ” 18. Attribute Access Magic

    Python gives complete control over attribute access.

    • __getattr__(self, name) โ€” Occurs when attribute is missing
    • __getattribute__(self, name) โ€” Intercepts every attribute lookup
    • __setattr__(self, name, value) โ€” Intercepts setting attributes
    • __delattr__(self, name) โ€” Intercepts deletion

    Attribute Access Magic

    Dynamic attribute handling

    Try it Yourself ยป
    Python
    class DynamicObject:
        def __getattr__(self, name):
            return f"{name} does not exist"
    
    obj = DynamicObject()
    print(obj.foo)      # foo does not exist
    print(obj.bar)      # bar does not exist

    ๐Ÿงฉ 19. Emulating Functions With __call__

    Anything can behave like a function.

    Callable Objects

    Emulating functions

    Try it Yourself ยป
    Python
    class Multiply:
        def __init__(self, factor):
            self.factor = factor
    
        def __call__(self, x):
            return x * self.factor
    
    # Usage:
    double = Multiply(2)
    triple = Multiply(3)
    
    print(double(10))   # 20
    print(triple(10))   # 30

    Used by:

    • neural network layers
    • preprocessing pipelines
    • middleware wrappers
    • job schedulers
    • configurable callbacks

    ๐Ÿงฑ 20. Context Manager Magic

    Implement:

    • __enter__(self)
    • __exit__(self, exc_type, exc, tb)

    Context Manager

    Database connection example

    Try it Yourself ยป
    Python
    class DatabaseConnection:
        def __enter__(self):
            print("Opening connection...")
            return self
        
        def __exit__(self, exc, val, tb):
            print("Closing connection...")
        
        def query(self, sql):
            print(f"Executing: {sql}")
    
    with DatabaseConnection() as db:
        db.query("SELECT * FROM users")

    ๐Ÿ”ฅ 21. Descriptors โ€” The Hidden Power Behind @property

    Descriptors define how attributes behave.

    A descriptor is any object defining one or more of:

    • __get__(self, instance, owner)
    • __set__(self, instance, value)
    • __delete__(self, instance)

    Descriptors

    Custom attribute behavior

    Try it Yourself ยป
    Python
    class Logged:
        def __set_name__(self, owner, name):
            self.name = name
        
        def __get__(self, instance, owner):
            if instance is None:
                return self
            print(f"Accessed {self.name}")
            return instance.__dict__.get(self.name)
    
        def __set__(self, instance, value):
            print(f"Setting {self.name} to {value}")
            instance.__dict__[self.name] = value
    
    class User:
        name = Logged()
    
    u = User()
    u.name = "Alice"
    print(u.name)

    ๐Ÿงฌ 22. Properties Built on Top of Descriptors

    property is just a wrapper around descriptors.

    Properties

    Using @property decorator

    Try it Yourself ยป
    Python
    class User:
        def __init__(self, name):
            self._name = name
        
        @property
        def name(self):
            return self._name
        
        @name.setter
        def name(self, value):
            if not value:
                raise ValueError("Name cannot be empty")
            self._name = value
    
    u = User("Alice")
    print(u.name)
    u.name = "Bob"
    print(u.name)

    โšก 23. Slots โ€” Memory-Efficient Objects

    Use __slots__ to avoid dynamic dictionaries:

    Slots

    Memory-efficient objects

    Try it Yourself ยป
    Python
    class Point:
        __slots__ = ("x", "y")
        
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    p = Point(1, 2)
    print(f"Point: ({p.x}, {p.y})")
    
    # This would raise AttributeError:
    # p.z = 3  # Can't add new attributes!

    Benefits:

    • โœ” lower memory
    • โœ” faster attribute access
    • โœ” prevents accidental attributes

    โšก 24. Understanding the Python Object Lifecycle

    Every Python object goes through:

    1. Allocation โ†’ __new__
    2. Initialization โ†’ __init__
    3. Destruction โ†’ __del__

    Singleton Pattern

    Object lifecycle control

    Try it Yourself ยป
    Python
    class Singleton:
        _instance = None
    
        def __new__(cls):
            if not cls._instance:
                print("Creating new instance")
                cls._instance = super().__new__(cls)
            else:
                print("Returning existing instance")
            return cls._instance
    
    s1 = Singleton()
    s2 = Singleton()
    print(f"Same object: {s1 is s2}")

    ๐Ÿ”ฅ 25. Metaclasses โ€” The Most Advanced Python Feature

    A metaclass is the class of a class.

    Classes create objects. Metaclasses create classes.

    Default metaclass: type

    Metaclasses

    Creating classes dynamically

    Try it Yourself ยป
    Python
    class Meta(type):
        def __new__(mcls, name, bases, attrs):
            print(f"Creating class {name}")
            return super().__new__(mcls, name, bases, attrs)
    
    # Usage:
    class User(metaclass=Meta):
        pass
    
    class Admin(User):
        pass

    What metaclasses are used for IN REAL SYSTEMS

    • โœ” Django ORM
    • โœ” SQLAlchemy Models
    • โœ” Pydantic / FastAPI Models
    • โœ” TensorFlow Layers
    • โœ” Enum internals
    • โœ” Plugin systems
    • โœ” Service registries

    ๐Ÿงฉ 26. Class Decorators vs Metaclasses

    Class decorators modify the class after it's created:

    Class Decorators

    Modifying classes after creation

    Try it Yourself ยป
    Python
    registry = []
    
    def register(cls):
        registry.append(cls)
        return cls
    
    @register
    class User:
        pass
    
    @register
    class Admin:
        pass
    
    print(f"Registered classes: {registry}")

    Metaclasses modify the class while being created.

    When to use what:

    • Class decorator โ†’ simple modification
    • Metaclass โ†’ structural modification

    ๐Ÿง  27. Emulating Containers (Full Custom Collections)

    Custom Collections

    Bounded list example

    Try it Yourself ยป
    Python
    class BoundedList:
        def __init__(self, limit):
            self.data = []
            self.limit = limit
    
        def __len__(self):
            return len(self.data)
    
        def __getitem__(self, i):
            return self.data[i]
    
        def append(self, val):
            if len(self.data) >= self.limit:
                raise ValueError("List full")
            self.data.append(val)
    
        def __repr__(self):
            return f"BoundedList({self.data})"
    
    bl = BoundedList(3)
    bl.append(1)
    bl.append(2)
    bl.append(3)
    print(bl)
    # bl.app
    ...

    ๐Ÿงฑ 28. Making Your Objects Hashable

    For objects to be used as dictionary keys OR in sets:

    Implement:

    • __hash__
    • __eq__

    Hashable Objects

    Dictionary keys and sets

    Try it Yourself ยป
    Python
    class Point:
        __slots__ = ("x", "y")
        
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __hash__(self):
            return hash((self.x, self.y))
    
        def __eq__(self, other):
            return self.x == other.x and self.y == other.y
        
        def __repr__(self):
            return f"Point({self.x}, {self.y})"
    
    # Now can use as dict keys
    points = {Point(0, 0): "origin", Point(1, 1): "diagonal"}
    print(points[Point(0, 0)])

    ๐Ÿ” 29. Overriding Truthiness & Boolean Behavior

    Boolean Behavior

    Custom truthiness

    Try it Yourself ยป
    Python
    class Connection:
        def __init__(self, status):
            self.status = status
        
        def __bool__(self):
            return self.status == "ready"
    
    conn1 = Connection("ready")
    conn2 = Connection("disconnected")
    
    if conn1:
        print("Connection 1 is ready")
    if not conn2:
        print("Connection 2 is not ready")

    ๐Ÿงฌ 30. Controlling String Representations

    String Representations

    __repr__ vs __str__

    Try it Yourself ยป
    Python
    class User:
        def __init__(self, id, name):
            self.id = id
            self.name = name
        
        def __repr__(self):
            return f"User(id={self.id})"
    
        def __str__(self):
            return self.name
    
    u = User(1, "Alice")
    print(repr(u))  # User(id=1)
    print(str(u))   # Alice
    print(u)        # Alice (uses __str__)

    โš™๏ธ 31-34. Advanced Topics

    Additional advanced magic method patterns:

    • Dynamic Attribute Computation - Using __getattr__ for lazy loading
    • Proxy Objects - Forwarding magic methods
    • Custom Number Types - Money, Temperature classes
    • Full Domain Models - Complete custom types

    Custom Money Type

    Full domain model

    Try it Yourself ยป
    Python
    class Money:
        def __init__(self, amount):
            self.amount = amount
    
        def __add__(self, other):
            return Money(self.amount + other.amount)
    
        def __mul__(self, rate):
            return Money(self.amount * rate)
    
        def __repr__(self):
            return "Money(" + str(self.amount) + ")"
    
    m1 = Money(100)
    m2 = Money(50)
    print("m1 + m2 =", m1 + m2)
    print("m1 * 1.5 =", m1 * 1.5)

    ๐ŸŽ“ 35. Final Summary โ€” You Now Understand the Entire Python Data Model

    By mastering this lesson, you now understand:

    • โœ” operator overloading
    • โœ” full comparison system
    • โœ” container emulation
    • โœ” iterator/iterable protocols
    • โœ” attribute access magic
    • โœ” callable classes
    • โœ” context manager internals
    • โœ” descriptor protocol
    • โœ” property internals
    • โœ” slots memory optimization
    • โœ” object lifecycle
    • โœ” metaclass architecture
    • โœ” custom domain-specific types
    • โœ” proxy patterns
    • โœ” advanced debugging behaviors

    You now write Python the way framework authors, not beginners, write it.

    This knowledge places you firmly at the top 1% of Python engineers.

    ๐Ÿ“‹ Quick Reference โ€” Magic Methods

    MethodTriggered by
    __init__(self)Object creation: MyClass()
    __repr__(self)repr() and interactive shell display
    __len__(self)len(obj)
    __getitem__(self, key)obj[key] indexing
    __enter__ / __exit__with obj: context manager

    ๐ŸŽ‰ Great work! You've completed this lesson.

    You now control how your objects respond to built-in Python operations โ€” the same protocol powering NumPy, pandas, and SQLAlchemy.

    Up next: Operator Overloading โ€” make your custom types support +, -, <, and other operators naturally.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy โ€ข Terms of Service