1
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ Are you going to have different items in the components array for different items, or are all of them just going to have x y z? \$\endgroup\$
    – Snowbody
    Commented Nov 29, 2017 at 6:54
  • \$\begingroup\$ @Snowbody I edited my question to make it clearer. \$\endgroup\$
    – Fomalhaut
    Commented Nov 29, 2017 at 6:59

2 Answers 2

1
\$\begingroup\$

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)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Yes, more pythonic, but defeats potential performance improvements by using numpy objects. \$\endgroup\$
    – Daniel
    Commented Nov 29, 2017 at 9:12
  • 2
    \$\begingroup\$ Yes, numpy would be better but the OP wants still to have the comfort of the dot notation. Note that all what is related to numpy is added by the OP after I posted this. \$\endgroup\$ Commented Nov 29, 2017 at 9:24
0
\$\begingroup\$

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]

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.