You should avoid calling special methods like __len__ directly because the built‑in functions and operators that invoke them (like len(), in, str(), etc.) are the intended public API. They often do extra work that you’d lose by calling the dunder method yourself.
1. Fallback behaviour is lost
Many operations have a fallback mechanism that a direct __ call bypasses.
For example, item in container works even when __contains__ is not defined, because in will fall back to iteration via __iter__ or __getitem__. If you write container.__contains__(item) directly, you get an AttributeError:
class MyContainer: def __iter__(self): yield 1 yield 2 yield 3 obj = MyContainer() print(2 in obj) # True (uses iteration fallback) print(obj.__contains__(2)) # AttributeError: 'MyContainer' object has no attribute '__contains__'
The same principle applies elsewhere. The built‑ins handle the complexity so you don’t have to.
2. Type checks and validation are bypassed
Built‑in functions often verify the return value of a special method. len() for example ensures that __len__ returns an int ≥ 0, and raises a clear TypeError otherwise.
Calling obj.__len__() directly skips that check, potentially giving you a weird value that causes confusing bugs later.
class BadLen: def __len__(self): return "oops" b = BadLen() len(b) # TypeError: 'str' object cannot be interpreted as an integer b.__len__() # "oops" (no immediate error – trouble down the line)
3. Readability and convention
len(book) reads naturally.book.__len__() is an implementation detail leaking into your code.
Special methods exist for the interpreter, not for direct human use. They’re a contract the object fulfills so that the language can treat it like a standard sequence, mapping, etc. Using the public function makes your intent clearer and keeps your code “Pythonic”.
4. Consistency across types
Not every object has a __len__ method, but len() gives a consistent experience (it raises a uniform TypeError). Direct calls would give AttributeError on objects without the method, breaking polymorphic code that expects a TypeError.
Bottom line:
Just as you write item in container instead of container.__contains__(item), always use len(obj) instead of obj.__len__(), str(obj) instead of obj.__str__(), and so on. Let Python’s built‑in machinery do its job.
All Questions From This Chapter « Previously

No comments:
Post a Comment