9

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?

Hyperx837
  • 773
  • 5
  • 13
  • 3
    No you shouldn't. All "magic" methods have an equivalent built-in function or syntax for calling them – Iain Shelvington Apr 05 '20 at 05:17
  • 3
    You should when you need the specific job they do, but most magic methods are only responsible for *part* of what you might think their job is. For example, `__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. – user2357112 Apr 05 '20 at 05:26
  • 1
    One of those relatively rare times when the thing you "heard from one guy" was actually right. Now might be a good time to play the lottery. – Mad Physicist Apr 05 '20 at 05:31

3 Answers3

11

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))
S.B
  • 13,077
  • 10
  • 22
  • 49
2

I'm not a senior developer, but my experience says that you shouldn't call magic methods directly.

Magic methods should be used to override a behavior on your object. For example, if you want to define how does your object is built, you override __init__. Afterwards when you want to initialize it, you use MyNewObject() instead of MyNewObject.__init__().

For me, I tend to appreciate the answer given by Alex Martelli here:

When you see a call to the len built-in, you're sure that, if the program continues after that rather than raising an exception, the call has returned an integer, non-negative, and less than 2**31 -- when you see a call to xxx.__len__(), you have no certainty (except that the code's author is either unfamiliar with Python or up to no good;-).

If you want to know more about Python's magic methods, I strongly recommend taking a look on this documentation made by Rafe Kettler: https://rszalski.github.io/magicmethods/

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Nuno Nelas
  • 60
  • 12
1

No you shouldn't.

it's ok to be used in quick code problems like in hackerrank but not in production code. when I asked this question I used them as first class functions. what I mean is, I used xlen = x.__mod__ instead of xlen = lamda y: x % y which was more convenient. it's ok to use these kinda snippets in simple programs but not in any other case.

Hyperx837
  • 773
  • 5
  • 13