Skip to content

Dunder Methods

Dunder (double underscore) methods let you customize how your classes behave with Desi's built-in operators and functions.

Constructor: __new__

class Point:
    pub x: int
    pub y: int

    pub def __new__(self, x: int, y: int):
        self.x = x
        self.y = y

let p = Point(10, 20)

Indexing: __getitem__ and __setitem__

class MyList:
    pub mut data: list[int]

    pub def __new__(self, values: list[int]):
        self.data = values

    pub def __getitem__(self, index: int) -> int:
        return self.data[index]

    pub def __setitem__(self, index: int, value: int):
        self.data[index] := value

let arr = MyList([10, 20, 30])
print(arr[0])    # 10 (uses __getitem__)
arr[0] := 999    # Uses __setitem__
print(arr[0])    # 999

Important

Use := for mutation, not =. This is Desi's syntax for reassignment.

Length: __len__

class MyList:
    pub data: list[int]

    pub def __len__(self) -> int:
        return len(self.data)

let arr = MyList([10, 20, 30])
print(len(arr))  # 3

Membership: __contains__

class MySet:
    pub data: list[int]

    pub def __new__(self, values: list[int]):
        self.data = values

    pub def __contains__(self, item: int) -> bool:
        for x in self.data:
            if x == item:
                return true
        return false

let s = MySet([10, 20, 30])

if 20 in s:
    print("Found!")  # This runs

if 99 in s:
    print("Won't run")

String Representation: __repr__

class Point:
    pub x: int
    pub y: int

    pub def __repr__(self) -> str:
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"

let p = Point(10, 20)
print(p)  # Point(10, 20)

Comparison: __eq__, __ne__, __lt__, etc.

class Point:
    pub x: int
    pub y: int

    pub def __eq__(self, other: Point) -> bool:
        return self.x == other.x and self.y == other.y

let p1 = Point(10, 20)
let p2 = Point(10, 20)
let p3 = Point(5, 5)

print(p1 == p2)  # true
print(p1 == p3)  # false

Truthiness: __bool__

Define custom truthiness for use in if conditions:

class Container:
    pub mut count: int

    pub def __new__(self, n: int):
        self.count := n

    pub def __bool__(self) -> bool:
        return self.count > 0

let empty = Container(0)
let filled = Container(5)

if empty:
    print("has items")  # Won't run
else:
    print("empty!")     # Runs

if filled:
    print("has items")  # Runs

This is useful for container-like classes, optional wrappers, or any object where "truthy/falsy" makes semantic sense.

Arithmetic: __add__, __sub__, etc.

class Point:
    pub x: int
    pub y: int

    pub def __add__(self, other: Point) -> Point:
        return Point(self.x + other.x, self.y + other.y)

let p1 = Point(10, 20)
let p2 = Point(5, 10)
let p3 = p1 + p2  # Point(15, 30)

Slicing: __getslice__

class MyList:
    pub data: list[int]

    pub def __new__(self, values: list[int]):
        self.data = values

    pub def __getslice__(self, start: int, end: int) -> list[int]:
        return self.data[start:end]

let arr = MyList([10, 20, 30, 40, 50])
let slice = arr[1:4]  # Uses __getslice__ -> [20, 30, 40]

Iteration: __iter__ and __next__

Define custom iterators for use in for loops:

class Range:
    pub start: int
    pub end: int
    pub mut current: int

    pub def __new__(self, s: int, e: int):
        self.start := s
        self.end := e
        self.current := s

    pub def __iter__(self) -> Range:
        return self

    pub def __next__(self) -> Option<int>:
        if self.current < self.end:
            let val = self.current
            self.current := self.current + 1
            let result: Option<int> = Option.Some(val)
            return result
        let nothing: Option<int> = Option.Nothing
        return nothing

let range = Range(0, 5)
for i in range:
    print(i)  # 0, 1, 2, 3, 4

The iterator protocol: - __iter__(self) returns an iterator object (often self) - __next__(self) returns Option.Some(value) for each element, or Option.Nothing when exhausted

Summary

Dunder Syntax Description
__new__ Class() Constructor
__getitem__ obj[i] Index access
__setitem__ obj[i] := v Index assignment
__getslice__ obj[i:j] Slice access
__len__ len(obj) Custom length
__contains__ x in obj Membership
__bool__ if obj: Custom truthiness
__iter__ for x in obj: Return iterator
__next__ for x in obj: Yield next element
__repr__ print(obj) String representation
__eq__ a == b Equality
__add__ a + b Addition