Step 1 of 7 · Reading · ~3 min
Learn
Object-Oriented Programming
Special Methods and Dataclasses
By the end you can make your own class work with print, +, ==, sorted, set, len, in, for and with, and let @dataclass write the boilerplate.
You have been using dunder-powered syntax all along: len(x), x in y, for, with open(...), ==. Each calls a double-underscore method on the object; a namedtuple got its == and repr that way. Here you write them yourself.
Text: __repr__ and __str__
repr(x) is for developers and should be unambiguous. str(x) and print are for users and fall back to __repr__ when __str__ is missing.
Operators return new objects
a + b calls a.__add__(b). Return a new object, never mutate self. If a cannot handle b, return NotImplemented (a value, not an exception) so Python tries b.__radd__(a) before giving up.
sum([Money(100), Money(250)]) starts at 0 + Money(100), which is why __radd__ handles 0; the result is Money(350). Money(5) + "x" gives TypeError: unsupported operand type(s) for +: 'Money' and 'str'.
Equality and hashing
Defining __eq__ sets __hash__ to None:
The contract: equal objects must have equal hashes, so hash exactly the fields __eq__ compares. Break it and sets quietly miscount:
Ordering
Write __eq__ and __lt__, and @functools.total_ordering fills in <=, > and >=. Compare tuples of numbers, never strings: "10" < "9" is True, (10,) < (9,) is False.
Truthiness and containers
bool(x) uses __bool__, then __len__ (zero is falsy). len, [], in and for map to __len__, __getitem__, __contains__ and __iter__. Delegate to an inner list: __iter__ returns iter(self._items).
__call__ makes an instance callable: c(5) runs Counter.__call__(c, 5).
Context managers
with X() as s: calls __enter__ (its result is s), then always __exit__(exc_type, exc, tb), even when the body raises. A falsy return lets the exception continue; True swallows it.
@dataclass
For classes that mostly hold data, @dataclass generates __init__, __repr__ and __eq__ from annotated fields. Annotations declare fields but are not enforced.
tags: list = [] raises ValueError: mutable default ... use default_factory, the shared-default trap from functions. order=True adds ordering by field order. frozen=True blocks assignment (FrozenInstanceError) and makes instances hashable; plain @dataclass sets __hash__ to None, like your own __eq__.
Common mistakes
__eq__with no__hash__:set(items)raisesTypeError: unhashable type.- A
__hash__over other fields than__eq__: equal objects both stay in the set, so the count is too high. - Sorting by text:
1.10lands before1.9because"1" == "1"and then"1" < "9". __add__that raises on a foreign type instead of returningNotImplemented, so__radd__never gets a chance.
Your exercise
Implement Version so equality, hashing and ordering all use the parsed numbers; 1.0 and 1.0.0 must be one set member.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…