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.