There are benefits to not using magic methods directly:
1- Readability
Using built-in functions like len() is much more readable than its relevant magic/special method __len__(). Imagine a source code full of only magic methods instead of built-in function... thousands of underscores...
2- Comparisons
Comparison operators are more sophisticated than you might think.
class C: def __lt__(self, other): print('__lt__ called') class D: pass c = C() d = D() d > c d.__gt__(c)
I haven't implemented __gt__ for either of those classes, but in d > c when Python sees that class D doesn't have __gt__, it checks to see if class C implements __lt__. It does, so we get '__lt__ called' in output which isn't the case with d.__gt__(c).
3- Extra checks
class C: def __len__(self): return 'boo' obj = C() print(obj.__len__()) # -> boo print(len(obj)) # TypeError!
or:
class C: def __str__(self): return 10 obj = C() print(obj.__str__()) # -> 10 print(str(obj)) # TypeError!
As you see, when Python calls magic methods implicitly, it does some extra checks as well.
4- Speed
This is the least important, but using let's say len() on built-in data types such as str gives a little bit of speedup as compared to __len__():
from timeit import timeit string = 'abcdefghijklmn' n = 10_000_000 print(timeit("len(string)", globals=globals(), number=n)) print(timeit("string.__len__()", globals=globals(), number=n))
output (rounded to 2 places):
0.38 0.90
It's because of the lookup process (__len__ in the namespace), If you create a bound method before timing, it's gonna be faster.
bound_method = string.__len__ print(timeit("bound_method()", globals=globals(), number=n))
0.57
__add__is not responsible for the entirety of a+operation,__getattribute__is not the whole attribute access protocol, and not all iterables have an__iter__method.