views:

586

answers:

8

I'm coming from a C# background where this stuff is super easy—trying to translate into Python for Maya.

There's gotta' be a better way to do this. Basically, I'm looking to create a Vector class that will simply have x, y and z coordinates, but it would be ideal if this class returned a tuple with all 3 coordinates and if you could edit the values of this tuple through x, y and z properties, somehow.

This is what I have so far, but there must be a better way to do this than using an exec statement, right? I hate using exec statements.

class Vector(object):
    '''Creates a Maya vector/triple, having x, y and z coordinates as float values'''

    def __init__(self, x=0, y=0, z=0):
        self.x, self.y, self.z = x, y, z

    def attrsetter(attr):
        def set_float(self, value):
            setattr(self, attr, float(value))
        return set_float

    for xyz in 'xyz':
        exec("%s = property(fget=attrgetter('_%s'), fset=attrsetter('_%s'))" % (xyz, xyz, xyz))
A: 

I don't understand your problem. Could you give an example of how your class is used? How does it differs from a normal class with three properties?

Sjoerd
If you don't understand the question - do not post an answer.
anthares
+1  A: 

I don't really understand the question. You have a Vector which describes a point in space with 3 coordinates. Your implementation already allows you to change the values:

v = Vector()
v.x = 10 # now x is 10

why should it return a tuple? What would you use it for? That said, a tuple is immutable so can't be modified, but you could use a list. Changing the numbers in that list will not reflect in Vector though.

If you do need to ensure the type is a float, consider property setters:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        print "x set to ", value
        self._x = value

c = C()
c.x = 10

print c.x, c._x
extraneon
+3  A: 
Chris Lutz
+1 and I'd like to add that namedtuples can be used in inheritance. See http://docs.python.org/dev/library/collections.html#collections.namedtuple for more details.
Frank
`v.x = 10` gives "AttributeError: can't set attribute" -- tuples, named or not, are **immutable**!
Alex Martelli
-1 OP specified that they would like to be able to edit the properties
tgray
namedtuples do a have a _replace method which creates a new instance with specific attributes updated (eg. x = v._replace(x=4, z=6)). This could help you but it probably isn't great in terms of performance.
Menno Smits
+4  A: 

If I understand your question correctly, you want something like this ?

class Vector(object):

    def __init__(self, x=0, y=0, z=0):
        self._x, self._y, self._z = x, y, z

    def setx(self, x): self._x = float(x)
    def sety(self, y): self._y = float(y)        
    def setz(self, z): self._z = float(z)     

    x = property(lambda self: float(self._x), setx)
    y = property(lambda self: float(self._y), sety)
    z = property(lambda self: float(self._z), setz)

This uses _x, _y and _z to (internally) store the incoming values and exposes them via the use of property (with getters, setters); I abbreviated the 'getters' using a lambda statement.

Note that in Python it would be very common to manipulate these values (say: x, y, z) on the object itself directly (I guess you want ensure the explicit float casts?)

ChristopheD
FWIW, if you changed `__init__()` to `self.x, self.y, self.z = x, y, z`, the last three calls in the `Vector` class definition for creating properties could be simplified to just `x = property(lambda self: self._x, setx)`, etc since the values stored would always already be floating point.
martineau
+1  A: 

Is this what you're looking for?

class vector(object):
    def __init__(self, x,y,z):
        self.x = x
        self.y = y
        self.z = z

    # overload []
    def __getitem__(self, index):
        data = [self.x,self.y,self.z]
        return data[index]

    # overload set []
    def __setitem__(self, key, item):
        if (key == 0):
            self.x = item
        elif (key == 1):
            self.y = item
        elif (key == 2):
            self.z = item
        #TODO: Default should throw excetion

This is the most naive way of doing it. I'm sure some Python guru will come along sneer at my code and replace it with a one-liner.

Examples of this code:

v = vector(1,2,3)
v[1] = 4
v[2] = 5

v.x = 1
v.z= 66
Il-Bhima
Not sneering, but I would change the implementation of `__getitem__` to just `return (self.x, self.y, self.z)[index]`. If you really wanted to be sneaky, you could change `__setitem__` to `setattr(self, chr(ord('x') + key), item)`. Both of these one-liners ignore error checking, but it's only a serious issue for the `__setitem__` implementation (`__getitem__` should probably raise that exception anyway). But you get my last upvote for today.
Chris Lutz
Points well made. The 'sneering' comment was just tongue in cheek :)
Il-Bhima
A: 

I understand that

  1. you want to have a filter that transform the input values into floats
  2. you don't want to write the property three times

You could use the following code:

class Vector(object):
    def __init__(self, x,y,z):
         self._x = x

def mangle(name):
return '_'+name

for name in ['x','y','z']:
    def set_xyz(self, value):
        self.__setattr__(mangle(name), float(value))
    def get_xyz(self):
        return self.__getattribute__(mangle(name))
    prop = property(get_xyz, set_xyz)
    setattr(Vector,name, prop)
Olivier
+2  A: 
unutbu
very good! check out @martineau's solution. it's close to yours, but it's taken one step further by putting the _auto_setter and _auto_getter inside the metaclass.
sfjedi
I came up with at slightly more compact, but equivalent way to code the metaclass version (see latest edit) which could also be applied to this decorator version. It's difficult to post code here in a comment, so it'll have to be left as an exercise for the reader.
martineau
@unutbu, FWIW, the main reason I added the default argument values *wasn't* in an effort to simplify things, it was to make their definitions work **inside** the `for attr in props:` loop, however the way they were originally written, it was picking up the last value assigned to the `attr` variable for all three getters and setters inside the loop. Has to do with *free variables*, *closure*, and nested functions.
martineau
+3  A: 

Edit: I've modified the code with my answer a bit more from @unutbu's original to simplify it and make what is being done clearer. In the latest version, the @staticmethod's have been eliminated altogether and replaced with nested one-liners. The outer function and nested class have been renamed AutoFloatProperties and _AutoFloatProperties to reflect their specialized behavior of converting and storing the values assigned as floats. Despite all this, @unutbu's own revised answer using a class decorator instead of a metaclass is a slightly simpler solution, although the internals and usage are very similar.

def AutoFloatProperties(*props):
    '''metaclass'''
    class _AutoFloatProperties(type):
        # Inspired by autoprop (http://www.python.org/download/releases/2.2.3/descrintro)
        def __init__(cls, name, bases, cdict):
            super(_AutoFloatProperties, cls).__init__(name, bases, cdict)
            for attr in props:
                def fget(self, _attr='_'+attr): return getattr(self, _attr)
                def fset(self, value, _attr='_'+attr): setattr(self, _attr, float(value))
                setattr(cls, attr, property(fget, fset))
    return _AutoFloatProperties

class Vector(object):
    '''Creates a Maya vector/triple, having x, y and z coordinates as float values'''
    __metaclass__ = AutoFloatProperties('x','y','z')
    def __init__(self, x=0, y=0, z=0):
        self.x, self.y, self.z = x, y, z # values converted to float via properties

if __name__=='__main__':
    v=Vector(1,2,3)
    print(v.x)
    # 1.0
    v.x=4
    print(v.x)
    # 4.0
martineau
This is by far the cleanest solution I've seen! Thanks for the wonderful example and also citing your sources.
sfjedi
I think @unutbu's latest edit of his original answer which uses a class decorator instead of a metaclass might be a bit simpler. Actually, after re-reading the question, I wonder what exactly the OP meant by "it would be ideal if this class returned a tuple with all 3 coordinates". I don't think any answer here so far returns a tuple. Perhaps he'd like a method that returned one with the three values in it, or the special method `__call__` could be defined to return one. Another possibility would be to give the class an `__iter__` method so tuple() could be called on instances...
martineau
I like the changes you've made to `AutoFloatProperties`. That really is much simpler!
unutbu