Project: Vector Class with Overloaded Operators0%
Function Decorators Deep Dive

Project: Vector Class with Overloaded Operators

Beginner12 min readUpdated: Jul 11, 2026
Study Materials

Project: Vector Class with Overloaded Operators

In scientific computing, game development, graphics rendering, and physics simulation, multi-dimensional vectors are core mathematical primitives. In this project, we will construct a high-performance, production-grade N-dimensional Euclidean Vector class that leverages Python's Data Model and operator overloading protocols to provide an intuitive mathematical API.


1. Project Requirements & Architecture

The Vector class must implement the following capabilities:

  1. 1
    Dimensional Agnosticism: Support 2D, 3D, or N-dimensional coordinates.
  2. 2
    String Formatting:
  • __repr__: Formal executable representation Vector(1.0, 2.0, 3.0).
  • __str__: Mathematical notation ⟨1.0, 2.0, 3.0⟩.
  1. 1
    Container Protocols:
  • __len__: Number of dimensions.
  • __getitem__: Coordinate access by index (vec[0]) and slicing.
  • __iter__: Unpacking coordinates (x, y, z = vec).
  1. 1
    Unary & Magnitude Operators:
  • __abs__: Euclidean norm/magnitude ($\sqrt{\sum x_i^2}$).
  • __neg__: Direction inversion (-vec).
  • __bool__: False if zero-vector, True otherwise.
  1. 1
    Arithmetic Operators:
  • Addition (+, __add__, __radd__) and Subtraction (-, __sub__, __rsub__).
  • Scalar multiplication & Dot Product (*, __mul__, __rmul__).
  • Cross Product (@, __matmul__) for 3D vectors.
  • In-place mutation (+=, -=, *=).
  1. 1
    Comparisons:
  • __eq__: Exact component-wise equality.
  • @total_ordering: Magnitude-based relational sorting (<, <=, >, >=).
Output
Mathematical Operations Dispatch
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
Vector + Vector Vector * Scalar Vector @ Vector
(__add__) (__mul__) (__matmul__)
Component-wise Scalar scaling 3D Cross Product
Addition

2. Production Implementation

Python
from functools import total_ordering
import math
from typing import Iterator, Sequence, Union
 
@total_ordering
class Vector:
"""An immutable, multi-dimensional Euclidean mathematical vector."""
 
__slots__ = ("_components",)
 
def __init__(self, *components: Union[int, float]) -> None:
if not components:
raise ValueError("A Vector must have at least one dimension.")
# Store components as an immutable tuple of floats
self._components: tuple[float, ...] = tuple(float(c) for c in components)
 
# ---------------------------------------------------------
# String Representations & Inspection
# ---------------------------------------------------------
def __repr__(self) -> str:
comps = ", ".join(f"{c:.4g}" for c in self._components)
return f"Vector({comps})"
 
def __str__(self) -> str:
comps = ", ".join(f"{c:.2f}" for c in self._components)
return f"⟨{comps}⟩"
 
# ---------------------------------------------------------
# Container & Sequence Protocol
# ---------------------------------------------------------
def __len__(self) -> int:
"""Returns the dimensional degree of the vector."""
return len(self._components)
 
def __getitem__(self, index: Union[int, slice]) -> Union[float, "Vector"]:
"""Enables indexing (vec[0]) and slicing (vec[0:2])."""
result = self._components[index]
if isinstance(index, slice):
return Vector(*result)
return result
 
def __iter__(self) -> Iterator[float]:
"""Allows unpacking: x, y = Vector(1, 2)."""
return iter(self._components)
 
# ---------------------------------------------------------
# Unary Operations & Truthiness
# ---------------------------------------------------------
def __abs__(self) -> float:
"""Computes the Euclidean norm (magnitude) of the vector."""
return math.sqrt(sum(c ** 2 for c in self._components))
 
def __neg__(self) -> "Vector":
"""Inverts all vector components: -Vector(1, -2) -> Vector(-1, 2)."""
return Vector(*(-c for c in self._components))
 
def __bool__(self) -> bool:
"""Zero vectors are False; any non-zero vector is True."""
return any(c != 0.0 for c in self._components)
 
# ---------------------------------------------------------
# Binary Arithmetic Operators
# ---------------------------------------------------------
def __add__(self, other: "Vector") -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
if len(self) != len(other):
raise ValueError(f"Dimension mismatch: {len(self)}D cannot add to {len(other)}D.")
return Vector(*(a + b for a, b in zip(self._components, other._components)))
 
def __radd__(self, other: "Vector") -> "Vector":
return self.__add__(other)
 
def __sub__(self, other: "Vector") -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
if len(self) != len(other):
raise ValueError(f"Dimension mismatch: {len(self)}D cannot subtract {len(other)}D.")
return Vector(*(a - b for a, b in zip(self._components, other._components)))
 
def __rsub__(self, other: "Vector") -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
return other.__sub__(self)
 
def __mul__(self, other: Union[int, float, "Vector"]) -> Union["Vector", float]:
"""Handles scalar multiplication (vec * k) and dot product (vec * other_vec)."""
if isinstance(other, (int, float)):
return Vector(*(c * float(other) for c in self._components))
elif isinstance(other, Vector):
if len(self) != len(other):
raise ValueError("Dot product requires identical dimensions.")
return sum(a * b for a, b in zip(self._components, other._components))
return NotImplemented
 
def __rmul__(self, other: Union[int, float]) -> "Vector":
"""Handles reflected scalar multiplication (k * vec)."""
if isinstance(other, (int, float)):
return self.__mul__(other) # type: ignore
return NotImplemented
 
def __truediv__(self, scalar: Union[int, float]) -> "Vector":
if not isinstance(scalar, (int, float)):
return NotImplemented
if scalar == 0:
raise ZeroDivisionError("Cannot divide a Vector by zero.")
return Vector(*(c / float(scalar) for c in self._components))
 
def __matmul__(self, other: "Vector") -> "Vector":
"""Computes the 3D Cross Product using the @ operator."""
if not isinstance(other, Vector):
return NotImplemented
if len(self) != 3 or len(other) != 3:
raise ValueError("Cross product (@) is only defined for 3-dimensional vectors.")
ax, ay, az = self._components
bx, by, bz = other._components
return Vector(
ay * bz - az * by,
az * bx - ax * bz,
ax * by - ay * bx
)
 
# ---------------------------------------------------------
# Comparisons (Magnitude and Equality)
# ---------------------------------------------------------
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self._components == other._components
 
def __lt__(self, other: object) -> bool:
"""Compares vectors based on Euclidean magnitude."""
if not isinstance(other, Vector):
return NotImplemented
return abs(self) < abs(other)
 
def normalize(self) -> "Vector":
"""Returns a unit vector in the same direction."""
mag = abs(self)
if mag == 0:
raise ValueError("Cannot normalize a zero-length vector.")
return self / mag

3. Verification and Demonstration

Python
# 1. Instantiation and representations
v1 = Vector(3.0, 4.0, 0.0)
v2 = Vector(1.0, 2.0, 3.0)
 
print(f"v1 repr: {v1!r}")
print(f"v1 str: {v1}")
print(f"Dimension: {len(v1)}D")
 
# 2. Magnitude and Truthiness
print(f"Magnitude of v1 (|v1|): {abs(v1)}") # 5.0 (Pythagorean 3-4-5)
print(f"Is v1 non-zero? {bool(v1)}") # True
print(f"Zero vector bool: {bool(Vector(0, 0))}") # False
 
# 3. Addition and Subtraction
v_sum = v1 + v2
print(f"v1 + v2 = {v_sum}")
 
v_diff = v1 - v2
print(f"v1 - v2 = {v_diff}")
 
# 4. Scalar multiplication and Reflected scaling
scaled = v1 * 2.5
reflected_scaled = 2.5 * v1
print(f"v1 * 2.5 = {scaled}")
print(f"2.5 * v1 = {reflected_scaled}")
 
# 5. Dot Product (Vector * Vector)
dot_product = v1 * v2
print(f"v1 · v2 (Dot Product): {dot_product}")
 
# 6. Cross Product via @ operator
cross_prod = v1 @ v2
print(f"v1 × v2 (Cross Product): {cross_prod}")
 
# 7. Unpacking and Indexing
x_coord = v1[0]
first_two = v1[0:2]
print(f"Index 0: {x_coord}, Slice 0:2: {first_two}")
 
x, y, z = v1
print(f"Unpacked: x={x}, y={y}, z={z}")
 
# 8. Relational Comparisons via Magnitude
v_small = Vector(1, 1)
v_large = Vector(10, 10)
print(f"v_small < v_large: {v_small < v_large}") # True
print(f"Sorted vectors: {sorted([v_large, v1, v_small])}")

4. Key Takeaways and Architectural Patterns

  1. 1
    Slots Optimization: By declaring __slots__ = ("_components",), memory usage is slashed by bypassing the dynamic __dict__ overhead for millions of small math entities.
  2. 2
    Operator Symmetry: Pairing __mul__ with __rmul__ ensures that expressions like 2 * v succeed just as smoothly as v * 2.
  3. 3
    Strict Domain Boundaries: Dunder methods like __matmul__ check dimensions (len == 3) early, raising informative ValueError exceptions before mathematical corruption occurs.

Multiple Choice Questions

1.

In the Vector class implementation, why is __rmul__ necessary in addition to __mul__? A. To support vector division B. To allow multiplication when the scalar is on the left-hand side (e.g., 3 * vector) C. To handle dot products between two vectors D. To support in-place augmented multiplication (vector *= 3)

Answer: B
Explanation:In the expression 3 * vector, Python calls int.__mul__(3, vector). Because int does not know how to multiply a Vector, it returns NotImplemented. Python then invokes vector.__rmul__(3).

2.

Which special method is invoked when evaluating the Euclidean magnitude of a vector using abs(vec)? A. __magnitude__ B. __norm__ C. __abs__ D. __math__

Answer: C
Explanation:Python maps the built-in function abs(obj) directly to the __abs__(self) dunder method.

3.

What operator is overloaded by implementing __matmul__(self, other)? A. Modulo % B. Matrix multiplication / cross product @ C. Exponentiation ** D. Floor division //

Answer: B
Explanation:The @ symbol corresponds to the matrix multiplication protocol (__matmul__), which is widely used in scientific libraries like NumPy and PyTorch.

4.

How does the Vector class support tuple-like unpacking (e.g., x, y, z = vec)? A. By defining __unpack__ B. By defining __iter__ which yields components sequentially C. By defining __repr__ D. By setting __slots__ = True

Answer: B
Explanation:Python's iterable unpacking syntax relies on the iteration protocol (__iter__). When __iter__ returns an iterator yielding the items, Python unpacks them directly into the target variables.

5.

What happens if two vectors of different dimensions (e.g., a 2D vector and a 3D vector) are added together in our implementation? A. The missing dimension is automatically padded with zeros. B. A ValueError is raised with a dimension mismatch message. C. A TypeError is raised. D. Only the first two coordinates are added, discarding the third.

Answer: B
Explanation:Our implementation explicitly checks if len(self) != len(other): raise ValueError(...), preventing invalid mathematical additions across mismatched dimensions.

Next Lesson

Function Decorators Deep Dive

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext