Well, let's assume that the "expected" behaviour holds, shall we?
Let's open up a python REPL and try
>>> 'caterpillar' * 2
'caterpillarcaterpillar'
OK, now for something different:
>>> [1, 2, 3] * 2
[1, 2, 3, 1, 2, 3]
Marvellous! How about this then:
>>> True * 2
2
Wait, what? Hm.
>>> False * 2
0
Whoops! Implicit type conversion takes place...
Even worse:
>>> 'abc' + 'efg'
'acbefg'
>>> 'efg' + 'abc'
'efgabc'
Now I'm stumped. Isn't addition supposed to be commutative?
So yeah, without contracts in place, operator overloading is BAD. You can never know what the operator does, or what its properties are by just looking at how it's used. There's simply no enforced rules and so no-one's stopping you from doing
>>> class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
return Complex(self.real - other.real, self.imag - other.imag)
def __repr__(self):
return f'Complex({self.real}+{self.imag})'
>>> x = Complex(1, 2)
>>> y = Complex(1, 2)
>>> x + y
Complex(0+0j)
Now this intentionally being malicious of course, but plenty of libraries overload operators in non-intuitive ways so that the operator's properties and behaviour isn't obvious. This is especially true if commutative operators are implemented as being non-commutative (e.g. abusing '+' for concatenation instead of using another symbol like '&' for example) or if the behaviour changes depending on the order of operands.