Course Content
Python Essentials for AI Engineer
6 sections · 48 lessons
What are Dunder (Magic) Methods?
What you need to know
Syntax to method
| You write | Python calls |
|---|---|
Model(...) | __new__, then __init__ |
repr(x), the REPL | x.__repr__() |
str(x), print(x) | x.__str__() (falls back to __repr__) |
len(x) | x.__len__() |
x[i] | x.__getitem__(i) |
for item in x | x.__iter__() |
a + b, a == b | a.__add__(b), a.__eq__(b) |
x(...) | x.__call__(...) |
with x: | x.__enter__() and x.__exit__(...) |
A small vector class
1class Vec:2 def __init__(self, *values):3 self.values = list(values)4 def __repr__(self):5 return f"Vec{tuple(self.values)}"6 def __len__(self):7 return len(self.values)8 def __add__(self, other):9 if not isinstance(other, Vec):10 return NotImplemented # let Python try other.__radd__ or raise TypeError11 return Vec(*(a + b for a, b in zip(self.values, other.values)))12 def __eq__(self, other):13 return isinstance(other, Vec) and self.values == other.values1415a, b = Vec(1, 2), Vec(10, 20)16print(a + b, len(a), a == Vec(1, 2)) # Vec(11, 22) 2 True17print(Vec.__hash__) # None -> defining __eq__ removed hashingReturn NotImplemented (not False or an exception) when an operation doesn't support the other type; Python then tries the other operand and finally raises a clear TypeError.
__repr__ vs __str__
__repr__ is for developers: unambiguous, ideally looks like the code to rebuild the object. __str__ is for end users. If you write only one, write __repr__.
A real-life example
PyTorch's Dataset contract is pure dunder methods: a dataset is any object with __len__ and __getitem__, and the DataLoader uses them to shuffle and batch. Here is the same idea for a fine-tuning set of support tickets, in plain Python:
1class TicketDataset:2 def __init__(self, rows):3 self.rows = rows4 def __len__(self):5 return len(self.rows)6 def __getitem__(self, i):7 text, label = self.rows[i]8 return {"text": text.strip().lower(), "label": label}9 def __repr__(self):10 return f"TicketDataset(n={len(self)})"1112ds = TicketDataset([(" Refund pending ", 1), ("Change address", 0), ("UPI failed ", 1)])13print(ds, len(ds)) # TicketDataset(n=3) 314print(ds[0]) # {'text': 'refund pending', 'label': 1}15print([row["label"] for row in ds]) # [1, 0, 1]The last line works even though there is no __iter__: when a class has __getitem__, Python iterates by calling it with 0, 1, 2, ... until IndexError. Likewise, model(x) in PyTorch works because nn.Module defines __call__, which runs hooks and then your forward.
Follow-up questions to expect
- "What is the difference between
__str__and__repr__?" —__repr__is the developer view used in the REPL and logs;__str__is the user-facing text used byprint.str()falls back to__repr__. - "What does
__call__do?" — It makes instances callable like functions, which is howmodel(x)works in PyTorch. - "Why did my objects become unhashable after adding
__eq__?" — Python sets__hash__toNonewhen you define__eq__, because equal objects must have equal hashes. Define__hash__too, or use@dataclass(frozen=True).