-1

I am a bit of a Python newbie regarding classes. I have seen that some scripts include lines which appear to "multiply" instances of classes together. For example:

Z = X * Y

where X and Y are instances of two different classes.

What does the * symbol mean and how is it applied to classes (or instances of classes) in Python?

In most cases * pertains to multiplication, but I don't understand what it means to "multiply" classes.

Any info is appreciated.

Adrian W
  • 4,563
  • 11
  • 38
  • 52
Darcy
  • 619
  • 2
  • 5
  • 15
  • 2
    See also https://stackoverflow.com/questions/6892616/python-multiplication-override – Neal Fultz Jul 11 '18 at 20:35
  • 4
    Classes and instances of classes are completely different concepts. The terms are not interchangeable. – user2357112 Jul 11 '18 at 20:35
  • It's up to the class itself to determine what it means to multiply instances, by defining the [`__mul__`](https://docs.python.org/3/reference/datamodel.html#object.__mul__) method. – Daniel Roseman Jul 11 '18 at 20:42
  • It's just convenient syntax for a binary operation taking two operands. You can make it mean whatever you like. It's good to make it make sense though and can improve the readability of code. If it isn't a good abstraction it can make code more difficult to understand as well, so use wisely. – Peter Wood Jul 11 '18 at 20:48

1 Answers1

3

The * operator just means "multiply".

But what it means for two objects to be multiplied is up to those objects' types to decide.

For all of the builtin and stdlib types, it means multiplication if that makes sense, or it's a TypeError if that doesn't make sense. For example:

>>> 2 * 3
6
>>> (1, 2, 3) * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)
>>> "abc" * "def"
TypeError: can't multiply sequence by non-int of type 'str'

Since multiplication doesn't make sense for class objects, they're one of the kinds of things where it's a TypeError:

>>> class C: pass
>>> C * 2
TypeError: unsupported operand type(s) for *: 'type' and 'int'

If you (or a third-party library author) create a new type, you can decide what multiplication means. The way you do this is covered under Emulating numeric types in the documentation, but the short version is that you define a __mul__ method:

class MyNumberyThing:
    def __init__(self, n):
        self.n = n
    def __repr__(self):
        return f'MyNumberyType({self.n})'
    def __mul__(self, other):
        if isinstance(other, MyNumberyThing):
            return MyNumberyThing(self.n * other.n)
        elif isinstance(other, int):
            return MyNumberyThing(self.n * other)
        elif isinstance(other, float):
            raise TypeError("sorry, can't multiply by float because of precision issues")
        else:
            raise TypeError(f"sorry, don't know how to multiply by {type(other).__name__}")

Notice that this makes instances of MyNumberyThing multiplicable. It doesn't make MyNumberyThing itself multiplicable (MyNumberyThing isn't a MyNumberyThing, it's a type):

>>> n = MyNumberyThing(2)
>>> n * 3
MyNumberyType(6)
>>> MyNumberyThing * 3
TypeError: unsupported operand type(s) for *: 'type' and 'int'

Of course nothing's stopping you from defining something ridiculous:

class MySillyThing:
    def __mul__(self, other):
        self.storage = -other
        print(f'I took your {other}')

>>> silly = MySillyThing()
>>> silly * 3
I took your 3
>>> silly.storage
-3

… except, of course, for the fact that nobody would understand your code. (And that "nobody" includes you, 6 months later trying to debug something you thought you were done with…)


As a side note, Python actually has two ways to spell "multiply": * and @.

The builtin types all ignore @, so you'll just get a TypeError if you try to use it. What it's there for is, basically, so NumPy can use * for elementwise multiplication, and @ for matrix multiplication. (The @ operator's magic method is even called __matmul__.) But of course other third-party libraries that similarly needed to do two different kinds of multiplication could use @ for the second kind.

abarnert
  • 354,177
  • 51
  • 601
  • 671