IBronchart
IBronchart

Reputation: 55

Python operator overloading not working

I have a Vector2 class:

class Vector2():
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector2(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector2(self.x - other.x, self.y - other.y)

    def __mul__(self, other):
        return Vector2(self.x * other, self.y * other)

    def __neg__(self):
        return Vector2(-self.x, -self.y)

    def magnitude(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)

    @classmethod
    def distance(self, v1, v2):
        return math.sqrt((v2.x - v1.x) ** 2 + (v2.y - v1.y) ** 2)

    def normalize(self):
        return self * (1/self.magnitude())

When I try to do 1.0 * Vector2(), I get the error: TypeError: unsupported operand type(s) for *: 'float' and 'instance'

However, sometimes it works as intended:

#this works as intended, s is a float
ball.pos -= ball.vel.normalize() * s

ball.vel is a vector and I am able to multiply by a float. Vectors are multiplied by floats in many sections of my code without errors.

Does anyone know where this inconsistency comes from?

Thanks

Upvotes: 1

Views: 1196

Answers (2)

gcr
gcr

Reputation: 463

This is for anyone else who stumbles onto this question. I was learning Operator Overloading (to learn how SQLalchemy could possibly return a BinaryExpression from the addition of two columns). Anyway, my simple code looked like it should have worked. I was running it inside of a python playground (I won't name names) and it kept failing/would not even run. It gave me an error message 'Suspension error, cannot call a function that blocks or suspends here on line X in main.py'.

After 5 minutes, I tried another playground and I got what seemed like the same issue. It started to run without any error but didn't do anything and just timed out, so I figured it was the same deal - i.e. my code.

I spent 20 minutes reading S/O and trying to debug and try different combinations. The operator overload was not even being called (as I ran print('add called'). I was ready to pull my hair out but I tried a third playground, and everything worked as expected to a T, in all the combinations I thought it would.

I copied the same exact code to the other playgrounds and they continued to fail in the same way, so it wasn't the code after all. BTW it also worked in a local script.

I couldn't tell what version of Python these playgrounds were running, but I assume it was at least Python3. In order to give you more info on that, I tried to print(sys.version) (after importing sys of course), and even this failed in the same two playgrounds that failed, but worked in the one that worked. I don't know what their issues were but that's how it was.

The bottom line is you (apparently) can't trust these python playgrounds to work, even if they're near the top of your search results. WYSINWYG. I just wanted to put this out there in case anyone who needs this stumbles on it. This too is part of the journey.

Upvotes: 1

gilch
gilch

Reputation: 11651

Define an __rmul__ method to get a_float * a_vector working. It can be as simple as

def __rmul__(self, other):
    return self * other

The other operators also have a dunder-r version. These reflected operators are called when the normal version is not defined for the given types. See the docs for the NotImplemented builtin constant.

The expression a * b is equivalent to a.__mul__(b), unless b is an instance of a subclass of a's class or a.__mul__(b) returns NotImplemented, in which case it's b.__rmul__(a) instead.

Upvotes: 1

Related Questions