Skip to Content
Course content

124: Magic Methods for Arithmetic Operators

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a small financial tool the other day, and I ran into a problem that I think is a perfect way to introduce how Python handles math with custom objects. I had a Money class to track balances, because using raw floats for currency is a recipe for rounding disasters.

The "TypeError" Wall

I started with a simple class. I just wanted to be able to add two money objects together to get a total. Here is what my first attempt looked like:

class Money:
    def __init__(self, amount, currency="USD"):
        self.amount = amount
        self.currency = currency

    def __repr__(self):
        return f"{self.amount} {self.currency}"

wallet_a = Money(50)
wallet_b = Money(20)

print(wallet_a + wallet_b)

I hit run, and predictably, Python screamed at me: TypeError: unsupported operand type(s) for +: 'Money' and 'Money'. This makes sense. Python knows how to add two integers or two strings, but it has no idea what "adding" two Money objects actually means. Should it add the amounts? Should it concatenate the currency strings? It doesn't want to guess, so it just crashes.

Teaching Python how to add

To fix this, I need to tell Python exactly what to do when it sees the + symbol between two Money instances. This is where "magic methods" (or dunder methods, because of the double underscores) come in. For addition, the method is __add__.

Let's try implementing it. I'll add the logic to the class, but I'll also add a quick check to make sure we aren't trying to add USD to EUR, because that would be a financial nightmare.

class Money:
    def __init__(self, amount, currency="USD"):
        self.amount = amount
        self.currency = currency

    def __repr__(self):
        return f"{self.amount} {self.currency}"

    def __add__(self, other):
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies!")
        return Money(self.amount + other.amount, self.currency)

wallet_a = Money(50)
wallet_b = Money(20)
print(wallet_a + wallet_b) # Now it prints: 70 USD

That worked. The magic is that when I write wallet_a + wallet_b, Python is actually calling wallet_a.__add__(wallet_b) behind the scenes. I'm effectively "overloading" the operator.

Expanding the toolkit

Of course, once addition works, I realized I also need to be able to subtract and multiply. I could write a method called subtract(), but then I'd have to write wallet_a.subtract(wallet_b), which feels clunky. I want to use - and *.

Following the same pattern as __add__, I can use __sub__ for subtraction and __mul__ for multiplication. Watch how this evolves:

class Money:
    def __init__(self, amount, currency="USD"):
        self.amount = amount
        self.currency = currency

    def __repr__(self):
        return f"{self.amount} {self.currency}"

    def __add__(self, other):
        if self.currency != other.currency:
            raise ValueError("Currency mismatch")
        return Money(self.amount + other.amount, self.currency)

    def __sub__(self, other):
        if self.currency != other.currency:
            raise ValueError("Currency mismatch")
        return Money(self.amount - other.amount, self.currency)

    def __mul__(self, multiplier):
        # Multiplying money by money is weird, 
        # but multiplying money by a number makes sense (e.g., 3x the cost)
        return Money(self.amount * multiplier, self.currency)

price = Money(10)
tax = Money(2)
total = (price + tax) * 3
print(total) # 36 USD

Dealing with the "Wrong" side

Here is where I hit a snag. I tried to do this: print(10 * price). It crashed. Why? Because 10 is an integer, and the integer class doesn't know how to multiply itself by a Money object. Python looks at the left-hand side first, fails, and then looks for a "reflected" version of the method on the right-hand side.

If __mul__ handles Money * something, then __rmul__ (Right-multiply) handles something * Money. I just need to point it back to the original logic.

    def __rmul__(self, multiplier):
        return self.__mul__(multiplier)

Now 10 * price works exactly like price * 10. It's a small addition, but it makes the class feel like a first-class citizen in the Python language.




📋 Practical Task

Implementing a 2D Vector Math Class

In game development or physics simulations, we often use 2D Vectors to represent positions or velocities. Your task is to build a Vector2D class that allows for intuitive mathematical operations.

Requirements:

  • The class should be initialized with x and y coordinates.
  • Implement __add__ so that adding two vectors adds their corresponding x and y components (e.g., Vector2D(1, 2) + Vector2D(3, 4) results in a Vector2D(4, 6)).
  • Implement __sub__ to handle vector subtraction.
  • Implement __mul__ to allow a vector to be scaled by a number (e.g., Vector2D(2, 3) * 3 results in Vector2D(6, 9)).
  • Implement __rmul__ so that scaling works regardless of whether the number is on the left or the right.
  • Implement __repr__ so that the vector prints clearly, such as Vector(x, y).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.