Python vector framework
Author: perry
A Python Vector Library #
In preparation for simulation #
In order to design a 6DOF simulation for vehicle testing, we will need a Vector3 object to store 3-dimensional values. Why 3? Think x, y, z; pitch, yaw, and roll. A 6DOF simulation has three axes, so vector values have the same. We begin with the class header:
class Vector3:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
But this setup is not very convenient, and also lacks any form of type hints or guarantees. Let’s spice it up a bit!
@dataclass
class Vector3:
x : float = 0
y : float = 0
z : float = 0
def __init__(self, x : float = 0, y : float = 0, z : float = 0) -> None:
self.x = float(x)
self.y = float(y)
self.z = float(z)
This class definition and constructor will take advantage of Python’s type hinting, as well as the @dataclass decorator, which allows linters like pylance to report member variables of a class at dev time. The type hints also allow type linters like mypy to scan the file and uncover any potential mismatches, but in the absence of mypy, Python will still accept non-float’s for x, y, and z, so they must be explicitly cast.
For ease of use later on, we will also add a factory function to generate a zero-value Vector3:
@classmethod
def Zero(cls) -> Vector3:
return cls(0, 0, 0)
However, at this point, we run into a fun problem with python’s type hinting. Types currently cannot be used for hinting until they are complete, which means you can’t say, specify that a method in Vector3 will return a Vector3 object. This will be changed in coming updates to Python, but it can be worked around by simply adding the line
from __future__ import annotations
to the imports of your project.
The @dataclass decorator will automatically generate __str__ and __repr__ functions for us, but the format is somewhat less than desirable, so we’ll override them with our own:
def __str__(self) -> str:
return ''.join(["(", str(self.x), ",",
str(self.y), ",", str(self.z), ")"])
def __repr__(self) -> str:
return ''.join(["Vector3(", str(self.x), ",",
str(self.y), ",", str(self.z), ")"])
And for comparisons, to allow for checking state variables and other generic checks on Vector3 objects, we implement __eq__ and __ne__:
def __eq__(self, other : object) -> bool:
if not isinstance(other, Vector3):
return NotImplemented
return ((self.x == other.x) and (self.y == other.y) and (self.z == other.z))
def __ne__(self, other : object) -> bool:
if not isinstance(other, Vector3):
return NotImplemented
return ((self.x != other.x) or (self.y != other.y) or (self.z != other.z))
Python requires equality & inequality functions to accept any objects, so the first line of comparison/inequality functions must be a type check to avoid accessing nonexistent member variables. Returning NotImplemented flags the interpreter to check the other object’s type for a compatible comparison function, and is a uniquely Pythonic concept.
More code for the vector math functions to come in pt.2 of this post!