I know this one has been covered before, and perhaps isn't the most pythonic way of constructing a class, but I have a lot of different maya node classes with a lot @properties for retrieving/setting node data, and I want to see if procedurally building the attributes cuts down on overhead/mantinence.
I need to re-implement __setattr__ so that the standard behavior is maintained, but for certain special attributes, the value is get/set to an outside object.
I have seen examples of re-implementing __setattr__ on stack overflow, but I seem to be missing something.
I don't think i am maintaining the default functionality of setAttr
Here is an example:
externalData = {'translateX':1.0,'translateY':1.0,'translateZ':1.0}
attrKeys = ['translateX','translateY','translateZ']
class Transform(object):
def __getattribute__(self, name):
print 'Getting --->', name
if name in attrKeys:
return externalData[name]
else:
raise AttributeError("No attribute named [%s]" %name)
def __setattr__(self, name, value):
print 'Setting --->', name
super(Transform, self).__setattr__(name, value)
if name in attrKeys:
externalData[name] = value
myInstance = Transform()
myInstance.translateX
# Result: 1.0 #
myInstance.translateX = 9999
myInstance.translateX
# Result: 9999 #
myInstance.name = 'myName'
myInstance.name
# AttributeError: No attribute named [name] #
!
This worked for me:
class Transform(object):
def __getattribute__(self, name):
if name in attrKeys:
return externalData[name]
return super(Transform, self).__getattribute__(name)
def __setattr__(self, name, value):
if name in attrKeys:
externalData[name] = value
else:
super(Transform, self).__setattr__(name, value)
However, I'm not sure this is a good route to go.
If the external operations are time consuming (say, you're using this to disguise access to a database or a config file) you may give users of the code the wrong impression about the cost. In a case like that you should use a method so users understand that they are initiating an action, not just looking at data.
OTOH if the access is quick, be careful that the encapsulation of your classes isn't broken. If you're doing this to get at maya scene data (pymel-style, or as in this example) it's not a big deal since the time costs and stability of the data are more or less guaranteed. However you'd want to avoid the scenario in the example code you posted: it would be very easy to assume that having set 'translateX' to a given value it would stay put, where in fact there are lots of ways that the contents of the outside variables could get messed with, preventing you from being able to know your invariants while using the class. If the class is intended for throwaway use (say, its syntax sugar for a lot of fast repetitive processing inside as loop where no other operations are running) you could get away with it - but if not, internalize the data to your instances.
One last issue: If you have 'a lot of classes' you will also have to do a lot of boilerplate to make this work. If you are trying to wrap Maya scene data, read up on descriptors (here's a great 5-minute video). You can wrap typical transform properties, for example, like this:
import maya.cmds as cmds
class MayaProperty(object):
'''
in a real implmentation you'd want to support different value types,
etc by storing flags appropriate to different commands....
'''
def __init__(self, cmd, flag):
self.Command = cmd
self.Flag = flag
def __get__(self, obj, objtype):
return self.Command(obj, **{'q':True, self.Flag:True} )
def __set__(self, obj, value):
self.Command(obj, **{ self.Flag:value})
class XformWrapper(object):
def __init__(self, obj):
self.Object = obj
def __repr__(self):
return self.Object # so that the command will work on the string name of the object
translation = MayaProperty(cmds.xform, 'translation')
rotation = MayaProperty(cmds.xform, 'rotation')
scale = MayaProperty(cmds.xform, 'scale')
In real code you'd need error handling and cleaner configuration but you see the idea.
The example linked above talks about using metaclasses to populate classes when you have lots of property descriptors to configure, that is a good route to go if you don't want to worry about all the boilerplate (though it does have a minor startup time penalty - I think that's one of the reasons for the notorious Pymel startup crawl...)