[EMAIL PROTECTED] > 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 -- http://mail.python.org/mailman/listinfo/python-list