问题
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] #
!
回答1:
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...)
回答2:
I have decided to go with @theodox s and use descriptors This seems to work nicely:
class Transform(object):
def __init__(self, name):
self.name = name
for key in ['translateX','translateY','translateZ']:
buildNodeAttr(self.__class__, '%s.%s' % (self.name, key))
def buildNodeAttr(cls, plug):
setattr(cls, plug.split('.')[-1], AttrDescriptor(plug))
class AttrDescriptor(object):
def __init__(self, plug):
self.plug = plug
def __get__(self, obj, objtype):
return mc.getAttr(self.plug)
def __set__(self, obj, val):
mc.setAttr(self.plug, val)
myTransform = Transform(mc.createNode('transform', name = 'transformA'))
myTransform.translateX = 999
As a side note...
It turns out my original code would have worked just by switching getattribute with getattr
no super needed
回答3:
Why not also do the same thing in __getattribute__
?
def __getattribute__(self, name):
print 'Getting --->', name
if name in attrKeys:
return externalData[name]
else:
# raise AttributeError("No attribute named [%s]" %name)
return super(Transform, self).__getattribute__(name)
Test code
myInstance = Transform()
myInstance.translateX
print(externalData['translateX'])
myInstance.translateX = 9999
myInstance.translateX
print(externalData['translateX'])
myInstance.name = 'myName'
print myInstance.name
print myInstance.__dict__['name']
Output:
Getting ---> translateX
1.0
Setting ---> translateX
Getting ---> translateX
9999
Setting ---> name
Getting ---> name
myName
Getting ---> __dict__
myName
回答4:
Here in your snippet:
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
See, in your __setattr__()
when you called for myInstance.name = 'myName'
, name
is not in attrKeys
, so it doesn't insert into externalData dictionary but it add into self.__dict__['name'] = value
So, when you try to lookup for that particular name, you don't ve into your externalData
dictionary so your __getattribute__
is raise with an exception.
You can fix that by changing the __getattribute__
instead of raising an exception change as below :
def __getattribute__(self, name):
print 'Getting --->', name
if name in attrKeys:
return externalData[name]
else:
return object.__getattribute__(self, name)
来源:https://stackoverflow.com/questions/22291337/python-re-implementing-setattr-with-super