Python Vector Class
Solution 1
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/#metaclass_examples)
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
Solution 2
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?)
Solution 3
I may be misreading your question, but I think what you want is already made for you in collections.namedtuple
:
>>> from collections import namedtuple
>>> Vector = namedtuple('Vector', 'x y z')
>>> v = Vector(0, 0, 0)
>>> v
Vector(x=0, y=0, z=0)
>>> v.x = 10
>>> v
Vector(x=10, y=0, z=0)
>>> tuple(v)
(10, 0, 0)
>>> v._asdict()
{'x': 10, 'y': 0, 'z': 0}
>>>
Does that look about right?
For shame, I forgot that tuples are immutable. Curse me for not upgrading from Python 2.5 so I could have actually tested the code I wrote. Anyway, you may want something quite similar to collections.namedtuple
, except more like a hypothetical namedlist
. Or you may want to discard that idea entirely and use something different. The point is that this answer was wrong, and I would delete it, except I feel obligated to the people who upvoted me to correct my mistake.
Solution 4
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
Solution 5
Edit: My previous answer tried to make a generalized AutoProperties metaclass which I hoped could be of general use. As @martineau's answer shows a solution specialized to the Vector
class can make things simpler.
Here's another idea along those lines (specialized simplicity over generalized complexity). It uses a class decorator (which I think is slightly simpler to understand than a metaclass) and @martineau's idea of simplifying the getters and setters with default values:
def AutoProperties(*props):
def _AutoProperties(cls):
for attr in props:
def getter(self,_attr='_'+attr):
return getattr(self, _attr)
def setter(self, value, _attr='_'+attr):
setattr(self, _attr, float(value))
setattr(cls,attr,property(getter,setter))
return cls
return _AutoProperties
@AutoProperties('x','y','z')
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 = map(float,(x, y, z))
Original answer: Here is a way to avoid repeating boiler-plate code when defining many similar properties.
I've tried to make the solution reasonably general, so it might be of use to people in other situations beside this particular one.
To use it you need to do 2 things:
- Put
__metaclass__=AutoProperties(('x','y','z'))
at the beginning of the definition of your class. You can list (as strings) as many attributes (e.g.
x
,y
,z
) as you wish.AutoProperties
will turn them into properties. - Your class, e.g.
Vector
, needs to define staticmethods_auto_setter
and_auto_getter
. They take one argument, the attribute name as a string, and return the setter or getter function, respectively, for that attribute.
The idea of using metaclasses to automatically set up properties comes from Guido Rossum's essay on properties and metaclasses. There he defines an autoprop
metaclass similar to what I use below. The main difference is that AutoProperties
expects the user to define getter and setter factories instead of manually defined getters and setters.
def AutoProperties(props):
class _AutoProperties(type):
# Inspired by autoprop (http://www.python.org/download/releases/2.2.3/descrintro/)
def __init__(cls, name, bases, cdict):
super(_AutoProperties, cls).__init__(name, bases, cdict)
for attr in props:
fget=cls._auto_getter(attr)
fset=cls._auto_setter(attr)
setattr(cls,attr,property(fget,fset))
return _AutoProperties
class Vector(object):
'''Creates a Maya vector/triple, having x, y and z coordinates as float values'''
__metaclass__=AutoProperties(('x','y','z'))
def __init__(self, x=0, y=0, z=0):
# I assume you want the initial values to be converted to floats too.
self._x, self._y, self._z = map(float,(x, y, z))
@staticmethod
def _auto_setter(attr):
def set_float(self, value):
setattr(self, '_'+attr, float(value))
return set_float
@staticmethod
def _auto_getter(attr):
def get_float(self):
return getattr(self, '_'+attr)
return get_float
if __name__=='__main__':
v=Vector(1,2,3)
print(v.x)
# 1.0
v.x=4
print(v.x)
# 4.0
Sam
Senior JavaScript Engineer JavaScript, TypeScript, HTML, CSS, Node, Gulp, C#, Animation
Updated on June 24, 2020Comments
-
Sam almost 4 years
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))
-
Frank about 14 years+1 and I'd like to add that namedtuples can be used in inheritance. See docs.python.org/dev/library/… for more details.
-
visual_learner about 14 yearsNot sneering, but I would change the implementation of
__getitem__
to justreturn (self.x, self.y, self.z)[index]
. If you really wanted to be sneaky, you could change__setitem__
tosetattr(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. -
Il-Bhima about 14 yearsPoints well made. The 'sneering' comment was just tongue in cheek :)
-
Alex Martelli about 14 years
v.x = 10
gives "AttributeError: can't set attribute" -- tuples, named or not, are immutable! -
tgray about 14 years-1 OP specified that they would like to be able to edit the properties
-
Sam over 13 yearsThis is by far the cleanest solution I've seen! Thanks for the wonderful example and also citing your sources.
-
Sam over 13 yearsvery 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.
-
Menno Smits over 13 yearsnamedtuples 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.
-
martineau over 13 yearsI 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 over 13 yearsI 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.
-
unutbu over 13 yearsI like the changes you've made to
AutoFloatProperties
. That really is much simpler! -
martineau over 13 years@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 theattr
variable for all three getters and setters inside the loop. Has to do with free variables, closure, and nested functions. -
martineau over 13 yearsFWIW, if you changed
__init__()
toself.x, self.y, self.z = x, y, z
, the last three calls in theVector
class definition for creating properties could be simplified to justx = property(lambda self: self._x, setx)
, etc since the values stored would always already be floating point. -
Grault almost 11 years-1. This code is broken in at least two obvious ways. It presents an interesting way to solve the problem, but it's sloppy and suspect, and below the standards I normally see on SO.