patternpythonMinor
A 3-D vector class built on top of numpy.array
Viewed 0 times
builttopnumpyarrayclassvector
Problem
I wanted a convenient class to easy access the parameters inside. I am using a lot of math in my game; that's why a wanted to access them through
It is a class which lies on top of numpy array. It basically means that I can control a numpy array trough my class. And using
For that I overwrite the built in functions.
Example:
to
So what I want to know is if this is a good way (I wanted a fast written implementation). Are there any performance issues and is it well written?
```
import numpy as np
from numbers import Number
class Vector3(object):
def __init__(self, x = 0, y = 0, z = 0, dtype = "float32"):
self._data = np.array([x,y,z], dtype = dtype)
self.x = self._data[0]
self.y = self._data[1]
self.z = self._data[2]
def __str__(self):
return str("Vector3({0.x},{0.y},{0.z})".format(self))
def __mul__(self, value):
if isinstance(value, type(self)):
result = self._data * value._data
elif isinstance(value, Number):
result = self._data * value
return type(self)(x = result[0], y = result[1], z = result[2])
def __rmul__(self, value):
return self.__mul__(value) # Kommutativgesetz/commutative
def __add__(self, value):
if isinstance(value, type(self)):
result = self._data + value._data
elif isinstance(value, Number):
result = self._data + value
return type(self)(x = result[0], y = result[1], z = result[2])
def __radd__(self, value):
return self.__add__(value) # Kommutativgesetz/commutative
def __sub__(self, value):
if isinstance(value, type(self)):
result = self._data - value._data
elif isinstance(value, Number
x, y and z (for readability). And this is my outcome.It is a class which lies on top of numpy array. It basically means that I can control a numpy array trough my class. And using
x, y and z for accessing the parameters instead of using indices ([0], [1] or [2]).For that I overwrite the built in functions.
Example:
a = numpy.array([1,1,1], dtype = "float32")
b = a[0]to
a = Vector3(1,1,1)
b = a.xSo what I want to know is if this is a good way (I wanted a fast written implementation). Are there any performance issues and is it well written?
```
import numpy as np
from numbers import Number
class Vector3(object):
def __init__(self, x = 0, y = 0, z = 0, dtype = "float32"):
self._data = np.array([x,y,z], dtype = dtype)
self.x = self._data[0]
self.y = self._data[1]
self.z = self._data[2]
def __str__(self):
return str("Vector3({0.x},{0.y},{0.z})".format(self))
def __mul__(self, value):
if isinstance(value, type(self)):
result = self._data * value._data
elif isinstance(value, Number):
result = self._data * value
return type(self)(x = result[0], y = result[1], z = result[2])
def __rmul__(self, value):
return self.__mul__(value) # Kommutativgesetz/commutative
def __add__(self, value):
if isinstance(value, type(self)):
result = self._data + value._data
elif isinstance(value, Number):
result = self._data + value
return type(self)(x = result[0], y = result[1], z = result[2])
def __radd__(self, value):
return self.__add__(value) # Kommutativgesetz/commutative
def __sub__(self, value):
if isinstance(value, type(self)):
result = self._data - value._data
elif isinstance(value, Number
Solution
Your code and text mismatch. You state that you want to use
Simplify overridden functions
You could also simplify your code some as almost all of the functions does the same:
And this could be called like:
Another question is why do you use
If however you actually mean
Or possibly even loose the
Consider adding/changing
In Python it's normal to add spaces after commas, something your
Implement a better test scheme
Your tests are executed but you only have a visual test of them. A much better option would be to use doctest. This would allow you to write tests in the header of each function, and you could be sure they all works as expected.
Something like the following:
And then in your main code you could simply do:
If all tests pass, you'll see nothing, and it they fail, you'll see why the fail and both the expected and actual results.
Properties
If you want to use
Which would output:
Do however remove the setting of
x, y and z, but you don't actually do it. You only use them in the initializer, and then you use them as named parameters when you recreate your object when returning the value. So your code kind of obfuscates that you are working on a numpy array.Simplify overridden functions
You could also simplify your code some as almost all of the functions does the same:
OPERATIONS = {
"add" : numpy.add,
"mul" : numpy.multiply,
"sub" : numpy.subtract,
...
}
def _apply_operation(self, value, operation):
if isinstance(value, type(self)):
result = OPERATIONS[operation](value._data, self._data)
elif isinstance(value, Number):
result = OPERATIONS[operation](value, self._data)
return type(self)(*result)And this could be called like:
def __add__(self, value):
return self._apply_operation(value, "add")Another question is why do you use
if ... elif, without any else. This does leave for potentially cases where neither of them hit, and this could lead to the result being undefined when returning.If however you actually mean
else instead of elif you simplify the calculation to:result = OPERATIONS[operation](value._data if isinstance(value, type(self))
else value,
self._data)Or possibly even loose the
result and do:return type(self)(*OPERATIONS[operation](value._data if isinstance(value, type(self))
else value,
self._data)Consider adding/changing
__repr__ and __str__In Python it's normal to add spaces after commas, something your
__str__ doesn't do. I would change the __str__ and add a __repr__ method like the following:def __str__(self):
return str('!r'.format(self))
def __repr__(self):
return 'Vector3({0}, {1}, {2})'.format(*self._data)Implement a better test scheme
Your tests are executed but you only have a visual test of them. A much better option would be to use doctest. This would allow you to write tests in the header of each function, and you could be sure they all works as expected.
Something like the following:
def __add__(self, value):
"""Adds the value into self, and returns result as Vector3.
>>> Vector3(1, 2, 3) + Vector3(2, 4, 6)
Vector3(3.0, 6.0, 9.0)
>>> Vector3(1, 2, 3) + 5
Vector3(6.0, 7.0, 8.0)
"""
return self._apply_operation(value, "add")And then in your main code you could simply do:
doctest.testmod()If all tests pass, you'll see nothing, and it they fail, you'll see why the fail and both the expected and actual results.
Properties
If you want to use
x, y and z as aliases into self._data you could use properties which changes self._data like the following:@property
def x(self):
"""Property x is first element of vector.
>>> Vector3(10, 20, 30).x
10.0
"""
return self._data[0]
@x.setter
def x(self, value):
self._data[0] = value
...
if __name__ == '__main__':
doctest.testmod()
a = Vector3(1, 2, 3)
a.x = 10.0
print('a = {}, a.z = {}'.format(a, a.x))Which would output:
a = Vector3(10.0, 2.0, 3.0), a.z = 10.0
Do however remove the setting of
self.x, self.y and self.z from __init__ so you don't have conflicting data variables.Code Snippets
OPERATIONS = {
"add" : numpy.add,
"mul" : numpy.multiply,
"sub" : numpy.subtract,
...
}
def _apply_operation(self, value, operation):
if isinstance(value, type(self)):
result = OPERATIONS[operation](value._data, self._data)
elif isinstance(value, Number):
result = OPERATIONS[operation](value, self._data)
return type(self)(*result)def __add__(self, value):
return self._apply_operation(value, "add")result = OPERATIONS[operation](value._data if isinstance(value, type(self))
else value,
self._data)return type(self)(*OPERATIONS[operation](value._data if isinstance(value, type(self))
else value,
self._data)def __str__(self):
return str('!r'.format(self))
def __repr__(self):
return 'Vector3({0}, {1}, {2})'.format(*self._data)Context
StackExchange Code Review Q#114409, answer score: 8
Revisions (0)
No revisions yet.