Python - 使用super重新实现__setattr__
我知道这个问题之前有人讨论过,可能不是最符合Python风格的类构建方式,但我有很多不同的Maya节点类,每个类都有很多@properties用来获取和设置节点数据。我想看看通过程序化的方式构建属性是否能减少开销和维护工作。
我需要重新实现__setattr__方法,以保持标准行为,但对于某些特殊属性,值需要设置为外部对象。
我在Stack Overflow上见过重新实现__setattr__的例子,但我似乎缺少了一些东西。
我觉得我没有保持setAttr的默认功能。
这里有一个例子:
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] #
!
4 个回答
为什么不在 __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)
测试代码
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']
输出结果:
Getting ---> translateX
1.0
Setting ---> translateX
Getting ---> translateX
9999
Setting ---> name
Getting ---> name
myName
Getting ---> __dict__
myName
在你的代码片段中:
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
看,在你的 __setattr__()
方法里,当你调用 myInstance.name = 'myName'
时,name
并不在 attrKeys
里面,所以它没有被放进 externalData
字典里,而是直接加到了 self.__dict__['name'] = value
里。
所以,当你试图查找这个特定的名字时,你并没有去 externalData
字典里找,因此你的 __getattribute__
方法就会抛出一个异常。
你可以通过修改 __getattribute__
来解决这个问题,具体的修改方式如下:
def __getattribute__(self, name):
print 'Getting --->', name
if name in attrKeys:
return externalData[name]
else:
return object.__getattribute__(self, name)
我决定采用@theodox的方法,使用描述符。
这样做效果不错:
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
顺便提一下...
其实我原来的代码只需要把getattribute换成getattr就可以正常工作了。
不需要用到super。
这个方法对我有效:
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)
不过,我不确定这是不是个好办法。
如果外部操作耗时较长(比如,你用这个来隐藏对数据库或配置文件的访问),可能会让使用这段代码的人产生错误的成本印象。在这种情况下,你应该使用一种方法,让用户明白他们是在发起一个操作,而不仅仅是在查看数据。
另一方面,如果访问速度很快,要小心不要破坏你类的封装性。如果你这样做是为了获取maya场景数据(像pymel那样,或者像这个例子),那就没什么大问题,因为时间成本和数据的稳定性基本上是有保障的。不过,你要避免你发布的示例代码中的情况:很容易就会假设设置了'translateX'为某个值后,它会保持不变,但实际上外部变量的内容可能会被改变,这样你就无法在使用这个类时知道你的不变条件。如果这个类是为了临时使用(比如,它是为了在一个没有其他操作的循环中进行快速重复处理的语法糖),那这样做是可以的——但如果不是,就要把数据内部化到你的实例中。
最后一个问题:如果你有“很多类”,你还需要做很多样板代码才能让这个工作。如果你想包装Maya场景数据,可以了解一下描述符(这里有个很棒的5分钟视频)。你可以像这样包装典型的变换属性:
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')
在实际代码中,你需要处理错误和更清晰的配置,但你明白这个思路了。
上面链接的例子提到使用 metaclass 来填充类,当你有很多属性描述符需要配置时,这是一条不错的路。如果你不想担心所有的样板代码(不过它确实会有一点启动时间上的损失——我觉得这就是著名的Pymel启动缓慢的原因之一…)