Custom array like implementation with components x, y, z

Posted on

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]

Leave a Reply

Your email address will not be published. Required fields are marked *