Python __mul__ Method
Last modified April 8, 2025
This comprehensive guide explores Python's __mul__
method, the
special method that implements multiplication operation overloading. We'll cover
basic usage, commutative operations, matrix multiplication, and practical examples.
Basic Definitions
The __mul__
method is called to implement the multiplication
operator (*
). When you write x * y
, Python attempts
to call x.__mul__(y)
.
Key characteristics: it must return the result of multiplication, can be
defined in any class, and should handle type checking. For commutative
operations, __rmul__
should also be implemented.
Basic __mul__ Implementation
Here's a simple implementation showing how to overload the multiplication operator for a custom class. This example creates a Vector class.
class Vector: def __init__(self, x, y): self.x = x self.y = y def __mul__(self, scalar): if isinstance(scalar, (int, float)): return Vector(self.x * scalar, self.y * scalar) return NotImplemented def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(2, 3) print(v * 3) # Vector(6, 9)
This example shows vector-scalar multiplication. The __mul__
method
checks if the right operand is a number before performing multiplication.
Returning NotImplemented
tells Python to try other methods like
__rmul__
on the right operand if available.
Implementing Commutative Multiplication
To handle cases where the left operand doesn't support multiplication but the
right one does, we implement __rmul__
.
class Vector: def __init__(self, x, y): self.x = x self.y = y def __mul__(self, scalar): if isinstance(scalar, (int, float)): return Vector(self.x * scalar, self.y * scalar) return NotImplemented def __rmul__(self, scalar): return self.__mul__(scalar) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(2, 3) print(v * 3) # Vector(6, 9) print(3 * v) # Vector(6, 9)
With __rmul__
implemented, both v * 3
and
3 * v
work correctly. __rmul__
is called when the
left operand doesn't know how to multiply with the right operand.
The implementation simply delegates to __mul__
since scalar
multiplication is commutative.
Matrix Multiplication with __matmul__
Python 3.5+ introduced the @
operator for matrix multiplication,
implemented via __matmul__
.
class Matrix: def __init__(self, data): self.data = data def __matmul__(self, other): if len(self.data[0]) != len(other.data): raise ValueError("Incompatible matrix dimensions") result = [[0] * len(other.data[0]) for _ in range(len(self.data))] for i in range(len(self.data)): for j in range(len(other.data[0])): for k in range(len(other.data)): result[i][j] += self.data[i][k] * other.data[k][j] return Matrix(result) def __repr__(self): return '\n'.join([' '.join(map(str, row)) for row in self.data)) A = Matrix([[1, 2], [3, 4]]) B = Matrix([[5, 6], [7, 8]]) print(A @ B)
This example implements proper matrix multiplication using the @
operator. The __matmul__
method performs the dot product of rows
and columns.
The implementation checks for compatible dimensions and creates a new matrix
with the result. This is distinct from element-wise multiplication which would
use __mul__
.
Element-wise Multiplication
For classes representing collections, you might want element-wise multiplication rather than matrix multiplication.
class Vector: def __init__(self, *components): self.components = components def __mul__(self, other): if isinstance(other, Vector): if len(self.components) != len(other.components): raise ValueError("Vectors must be same length") return Vector(*[a * b for a, b in zip(self.components, other.components)]) return NotImplemented def __repr__(self): return f"Vector{self.components}" v1 = Vector(1, 2, 3) v2 = Vector(4, 5, 6) print(v1 * v2) # Vector(4, 10, 18)
This implementation performs element-wise multiplication when both operands are vectors. It checks that the vectors are the same length before multiplying.
The result is a new vector where each component is the product of corresponding components in the input vectors.
Combining Different Types
The __mul__
method can handle operations between different types
when appropriate. Here's an example with units.
class Meter: def __init__(self, value): self.value = value def __mul__(self, other): if isinstance(other, (int, float)): return Meter(self.value * other) if isinstance(other, Meter): return self.value * other.value # returns area in square meters return NotImplemented def __rmul__(self, other): return self.__mul__(other) def __repr__(self): return f"{self.value}m" distance = Meter(5) print(distance * 2) # 10m print(3 * distance) # 15m area = distance * Meter(4) print(area) # 20
This example shows different behaviors based on the right operand's type. Multiplying by a number scales the distance, while multiplying two Meter instances returns an area.
The implementation demonstrates how a single operator can have different meanings depending on context while maintaining type safety.
Best Practices
- Type checking: Always verify operand types before operations
- Return NotImplemented: For unsupported types to enable fallback
- Implement __rmul__: For commutative operations
- Document behavior: Clearly specify supported operations
- Consider performance: Optimize for large data structures
Source References
Author
List all Python tutorials.