It's all about extra checks that python does with some built-in functions.
len() --> __len__() + some checks
str() --> __str__() + some checks
There is a difference between when "you" call a method explicitly or when that method gets called "by Python"! The point is when Python calls your method it will do some checks for you. (That's one of the reasons that we should use those built-in functions instead of calling relevant dunder method.)
We can see that behavior with len()
and __len__()
as well:
class Test:
def __len__(self):
return 'foo'
t = Test()
print(t.__len__()) # fine
print(len(t)) # TypeError: 'str' object cannot be interpreted as an integer
So python checked for returning integer in second print statement! that's what expected from __len__()
.
Same thing happens here. When you call print(t)
Python itself calls __str__()
method, so it does check to see if __str__()
returns a string that is expected or not. (same thing happens with str(t)
)
But, when you say print(t.__str__())
, first, you're calling it's __str__()
method on the instance explicitly by yourself, there is no checking here... what does get back ? number 5
, and then python will run print(5)
.