Python Multiplication Operator: Syntax and Behavior
python multiplication operator: Learn how the Python multiplication operator works with numbers, sequences, and custom classes, including precedence, overloading, and...
The python multiplication operator (*) is not limited to arithmetic. When you apply it to two numbers, it returns their product. When you apply it to a sequence and an integer, it repeats the sequence. When you apply it to custom objects, it invokes their __mul__ method. This article explains how the operator behaves across these contexts, where its precedence can surprise you, and what to consider when using it in performance-sensitive code.
Numeric Multiplication and Type Promotion
The most common use of * is numeric multiplication. With two integers, the result is an integer. With floats, the result is a float. Mixing an integer and a float produces a float because Python promotes the integer to a float before the operation.
print(3 * 4) # 12 print(3.5 * 2) # 7.0 print(3 * 2.0) # 6.0
Complex numbers also work naturally. The result follows the standard rules of complex arithmetic.
z1 = 2 + 3j z2 = 1 - 1j print(z1 * z2) # (5+1j)
The type promotion rule is important when you write generic code that accepts either integers or floats. If you rely on the result type to decide subsequent logic, remember that a float operand always yields a float result.
Sequence Repetition with Strings, Lists, and Tuples
When one operand is a sequence (string, list, tuple) and the other is an integer, * repeats the sequence that many times. This is often called sequence repetition.
print("ab" * 3) # ababab print([1, 2] * 2) # [1, 2, 1, 2] print((0, 1) * 3) # (0, 1, 0, 1, 0, 1)
The integer must be non-negative. Multiplying by zero produces an empty sequence of the same type. Multiplying by a negative integer also produces an empty sequence, not an error.
print("x" * 0) # (empty string) print("x" * -1) # (empty string)
This behavior is consistent across strings, lists, and tuples, but note that the repetition creates a new object. For lists, the elements are not copied; they are references to the same objects. This matters when the list contains mutable items.
a = [[1]] * 3 a[0].append(2) print(a) # [[1, 2], [1, 2], [1, 2]]
The inner list is shared, so modifying one element affects all repetitions. If you need independent copies, use a list comprehension instead.
Operator Precedence and Associativity
The * operator has higher precedence than + and -, but lower than ** (exponentiation). It associates left-to-right. This is standard arithmetic behavior, but it can cause confusion when combined with sequence operations.
print(2 + 3 * 4) # 14, not 20 print(2 * 3 ** 2) # 18, because ** binds tighter than *
For sequence repetition, the same precedence applies. For example, "a" + "b" * 3 evaluates "b" * 3 first, then concatenates: "abbb". If you intend to repeat the concatenation, parentheses are required.
print("a" + "b" * 3) # abbb print(("a" + "b") * 3) # ababab
Understanding precedence is essential when you write expressions that mix arithmetic and sequence operations. When in doubt, use parentheses to make the intent explicit.
Overloading Multiplication for Custom Classes
In Python, operators are implemented through special methods. The multiplication operator invokes __mul__ on the left operand. If that method returns NotImplemented, Python tries __rmul__ on the right operand. This allows you to define multiplication for your own classes.
class Vector: def __init__(self, x, y): self.x = x self.y = y def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar) def __rmul__(self, scalar): return self.__mul__(scalar) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(2, 3) print(v * 2) # Vector(4, 6) print(2 * v) # Vector(4, 6) via __rmul__
The __rmul__ method is necessary for the commutative case when the left operand is a built-in type that does not know about your class. Without it, 2 * v would raise a TypeError.
When implementing __mul__, decide whether the operator should accept only numbers or also other instances of the same class. The method should return NotImplemented for unsupported types so that Python can fall back to the right operand's method or raise an appropriate error.
Performance and Memory Considerations
Sequence repetition is implemented in C and is generally faster than an equivalent loop that appends elements one by one. However, it allocates a new sequence of the resulting length. For large repetitions, this can consume significant memory.
# Efficient for building a large string from a pattern pattern = "ab" result = pattern * 1_000_000
If you need to repeat a sequence many times in a loop, creating the repeated sequence once outside the loop avoids repeated allocations. For lists, be aware that repetition shares references to mutable elements, which can lead to unintended aliasing and higher memory usage if you later mutate those elements.
For numeric multiplication, the cost is minimal and not usually a performance concern. The main performance consideration is in sequence repetition, where the size of the result dominates the runtime.
Common Edge Cases and Type Errors
Multiplying a sequence by a non-integer raises a TypeError. For example, "a" * 1.5 fails because repetition requires an integer. Similarly, multiplying two sequences with * is not defined; use + for concatenation.
# TypeError: can't multiply sequence by non-int of type 'float' # print("a" * 1.5) # TypeError: can't multiply sequence by non-int of type 'str' # print("a" * "b")
When using custom classes, forgetting to implement __rmul__ leads to a TypeError when the multiplication is attempted with the custom object on the right side. Also, if __mul__ returns NotImplemented for a valid type, Python may raise a confusing error unless the right operand handles it.
Another edge case is multiplication by very large integers. For sequences, the result size is proportional to the integer, so a huge multiplier can exhaust memory. For numbers, Python's arbitrary-precision integers handle large values, but the operation may become slower as the numbers grow.