Search code examples
pythonmagic-methods

Should I use python magic methods directly?


I heard from one guy that you should not use magic methods directly. and I think in some use cases I would have to use magic methods directly. So experienced devs, should I use python magic methods directly?


Solution

  • I intended to show some benefits of 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- Comparison operators:

    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 neither 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__())  # fine
    print(len(obj))       # error
    

    or:

    class C:
        def __str__(self):
            return 10
    
    obj = C()
    print(obj.__str__())  # fine
    print(str(obj))       # error
    

    As you see, when Python calls that magic methods implicitly, it does some extra checks as well.


    4- This is the least important but using let's say len() on built-in data types such as str gives a little bit of speed as compared to __len__():

    from timeit import timeit
    
    string = 'abcdefghijklmn'
    
    print(timeit("len(string)", globals=globals(), number=10_000_000))
    print(timeit("string.__len__()", globals=globals(), number=10_000_000))
    

    output:

    0.5442426
    0.8312854999999999
    

    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=10_000_000))