Python's syntax is a thin skin over method calls. a + b is really
a.__add__(b); len(x) is x.__len__(); print(x) reaches
for x.__str__(). Define these double-underscore methods on your class and the
built-in operators "just work" on your own objects — Java's equals/toString,
but for every operator.
When the interpreter sees an operator or a built-in applied to an object, it translates it into a call to a
specially-named method on that object's type. There's no magic beyond the naming convention: implement the
right dunder and the corresponding syntax starts working; leave it out and Python either falls back to a
default or raises TypeError. Here's a Vector class — pick a piece of syntax and see
what Python actually calls:
v = Vector(1, 2) · w = Vector(3, 4)
__repr__ and __eq__Two dunders earn their keep on almost every class you write:
__repr__ controls how your object prints — in the REPL, in a list, in a debugger.
Without it you get the useless <Vector object at 0x10f3…>. Define it and every print
becomes informative. (__str__ is the "nice" version for end users;
__repr__ is the unambiguous one for developers, and is the fallback if
__str__ is missing.)__eq__ controls what == means. This one has a sharp edge: if you
don't define it, == falls back to identity — the same as is —
so two vectors with identical components compare unequal because they're different objects.
Toggle it:A related caution: if you define __eq__ you usually want
__hash__ too, so your objects still behave in dicts and
sets — Python ties equality and hashing together.
+→__add__, ==→__eq__, len()→__len__,
x[i]→__getitem__, print→__str__/__repr__.
Implement the dunder, get the syntax. Always add __repr__; if you add __eq__
remember == defaults to identity without it (and pair it with __hash__).