automatically assigning names to indexes

François Pinard pinard at iro.umontreal.ca
Tue Jul 12 09:38:10 EDT 2005


[simonwittber at gmail.com]

> I know its been done before, but I'm hacking away on a simple Vector
> class. [...] However, I'd like to add attribute access (magically),
> so I can do this: [...] Has anyone got any ideas on how this might be
> done?

I needed something this last week, while toying with rotations.  Allow
me to humbly offer my solution, meant for more than one class.  Yet for
brievety, I'm limiting my example to a single class.  A few constructors
are used in the example, but the corresponding classes are missing.

I hesitated a bit between having my rotation objects be modifiable or
not, and finally opted for the later (consequently, the constructor is
neatly called for a resulting object).  If you really want a modifiable
object, derive from `list' instead of deriving from `tuple', rename
`NamedTuple' into `NamedList', and wihin sub-classes, initialise your
object with `__init__(self, ...)' rather than with `__new__(cls, ...)'.



__metaclass__ = type
import math

# Almost zero, but not yet.
epsilon = 1e-9

from math import pi
half_pi = .5*pi

class NamedTuple(tuple):

    class __metaclass__(type):

        def __new__(cls, name, bases, definitions):
            self = type.__new__(cls, name, bases, definitions)
            if hasattr(self, '__names__'):
                def make_property(index):
                    def getter(self): return self[index]
                    return property(getter)
                for index, name in enumerate(self.__names__):
                    setattr(self, name, make_property(index))
            return self

class Quaternion(NamedTuple):
    __names__ = 'w', 'x', 'y', 'z'

    def __new__(cls, w, x, y, z):
        l = 1./math.sqrt(w*w + x*x + y*y + z*z)
        return cls.new(w*l, x*l, y*l, z*l)

    def new(cls, w, x, y, z):
        if w < 0.:
            self = tuple.__new__(cls, (-w, -x, -y, -z))
        else:
            self = tuple.__new__(cls, (w, x, y, z))
        assert self.is_normal(), self
        return self
    new = classmethod(new)

    def is_normal(self):
        # For debugging only.
        w, x, y, z = self
        return abs(w*w + x*x + y*y + z*z - 1.) < epsilon and w >= 0.

    def __eq__(self, other):
        w1, x1, y1, z1 = self
        w2, x2, y2, z2 = other
        return abs(w1-w2) + abs(x1-x2) + abs(y1-y2) + abs(z1-z2) < epsilon

    def __ne__(self, other):
        return not self == other

    def __mul__(self, other):
        w1, x1, y1, z1 = self
        w2, x2, y2, z2 = other
        return Quaternion.new(w1*w2 - x1*x2 - y1*y2 - z1*z2,
                              w1*x2 + x1*w2 + y1*z2 - z1*y2,
                              w1*y2 + y1*w2 - x1*z2 + z1*x2,
                              w1*z2 + z1*w2 + x1*y2 - y1*x2)

    def __div__(self, other):
        w1, x1, y1, z1 = self
        w2, x2, y2, z2 = other
        return Quaternion.new( w1*w2 + x1*x2 + y1*y2 + z1*z2,
                              -w1*x2 + x1*w2 - y1*z2 + z1*y2,
                              -w1*y2 + y1*w2 + x1*z2 - z1*x2,
                              -w1*z2 + z1*w2 - x1*y2 + y1*x2)

    def __rdiv__(self, other):
        if not isinstance(other, (int, long, float)):
            raise TypeError("unsupported operand type(s) for /")
        w, x, y, z = self
        return Quaternion.new(w, -x, -y, -z)

    __truediv__ = __div__
    __rtruediv__ = __rdiv__

    def euler(self):
        w, x, y, z = self
        x2 = x + x
        y2 = y + y
        z2 = z + z
        xx2 = x2*x
        yy2 = y2*y
        zz2 = z2*z
        wx2 = x2*w
        wy2 = y2*w
        wz2 = z2*w
        xy2 = x2*y
        yz2 = y2*z
        zx2 = z2*x
        siny = wy2 - zx2
        if abs(abs(siny) - 1) > epsilon:
            return Euler.new(math.asin(siny),
                             math.atan2(yz2 + wx2, 1. - xx2 - yy2),
                             math.atan2(xy2 + wz2, 1. - yy2 - zz2))
        if siny > 0.:
            y = half_pi
        else:
            y = -half_pi
        return Euler.new(math.atan2(-(yz2 - wx2), 1. - xx2 - zz2), y, 0.)

    def matrix(self):
        w, x, y, z = self
        x2 = x + x
        y2 = y + y
        z2 = z + z
        xx2 = x2*x
        yy2 = y2*y
        zz2 = z2*z
        wx2 = x2*w
        wy2 = y2*w
        wz2 = z2*w
        xy2 = x2*y
        yz2 = y2*z
        zx2 = z2*x
        return Matrix(1. - yy2 - zz2, xy2 + wz2,      zx2 - wy2,
                      xy2 - wz2,      1. - xx2 - zz2, yz2 + wx2,
                      zx2 + wy2,      yz2 - wx2,      1. - xx2 - yy2)

-- 
François Pinard   http://pinard.progiciels-bpi.ca



More information about the Python-list mailing list