Python - Re-Implementing __setattr__ with super

╄→гoц情女王★ 提交于 2019-12-20 02:45:12

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!