Problem
I want to implement a custom vector-like in Python that stores its components in a list (or another container). And I want to access the components as x
, y
and z
in order to get and set them. What is the best way to do it?
I implemented it like this:
import numpy as np
class Vector3d:
components = ['x', 'y', 'z']
def __init__(self):
self._data = np.array([0.0, 0.0, 0.0])
def __getattr__(self, key):
if key in self.components:
index = self.components.index(key)
return self._data[index]
else:
return super().__getattr__(key)
def __setattr__(self, key, value):
if key in self.components:
index = self.components.index(key)
self._data[index] = value
else:
return super().__setattr__(key, value)
def __repr__(self):
return repr(self._data)
def norm(self):
return np.linalg.norm(self._data)
a = Vector3d()
a.x = 1.2
a.y = 2.3
a.z = 3.4
print(a.x, a.y, a.z)
print(a)
print(a.norm())
Here are the aspects that I dislike about it:
- First, I duplicated the code
if key in self.components: index = self.components.index(key)
. - Second, searching for the index every time seems to be non-optimal towards the consuming time. I believe there’s a better way to implement it.
Please, suggest different approaches to me.
There may be many causes of doing it. For example, I may want to store the components as (ctypes.c_double * 3) array and to pass them to a DLL to gain performance of some calculations. Or to store them as numpy.array having the access to all numpy array methods. But I still want to keep the access to the components through x, y, z from the outside.
Solution
I suggest you to use a solution that relies on properties
notion. This is simpler and more efficient to implement. Here is what you can do:
class CustomArrayLike:
def __init__(self):
self._x = None
self._y = None
self._z = None
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = value
@property
def z(self):
return self._z
@x.setter
def z(self, value):
self._z = value
def __repr__(self):
return repr([self._x, self._y, self._z])
if __name__ == '__main__':
a = CustomArrayLike()
a.x = 1.2
a.y = 2.3
a.z = 3.4
print(a.x, a.y, a.z)
print(a)
The data type name should match its interface, not its implementation. Don’t put “array” in the name of the class.
I can think of two other ways to do it, each with their own advantages and disadvantages.
One would be to use the properties method as @Billal suggested, but to store things as an actual array:
class self.Point3D:
def __init__(self):
self._data = np.array([0.0,0.0,0.0])
@property
def x(self):
return self._data[0]
@x.setter
def x(self, value):
self.data[1] = value
@property
def y(self):
return self.data[1]
@y.setter
def y(self, value):
self.data[1] = value
@property
def z(self):
return self._data[2]
@x.setter
def z(self, value):
self._data[2] = value
def __repr__(self):
return repr(self._data)
The other way would also be to use properties to create pseudo-constants:
class self.CoordinateIndexes:
def __init__(self):
pass
@property
def x(self):
return 0
@property
def y(self):
return 1
@property
def z(self):
return 2
and then you can refer to components with e.g. a[CoordinateIndexes.x]