Python – Reimplementando __setattr__ con super

Sé que esta ha sido cubierta antes, y quizás no sea la forma más pirónica de construir una clase, pero tengo muchas clases diferentes de nodos maya con muchas propiedades para recuperar / configurar datos de nodos, y quiero ver Si la construcción de los atributos de manera procesal reduce la sobrecarga / mantenimiento.

Necesito volver a implementar __setattr__ para que se mantenga el comportamiento estándar, pero para ciertos atributos especiales, el valor es obtener / establecer en un objeto externo.

He visto ejemplos de la reimplementación de __setattr__ en el desbordamiento de stack, pero parece que me falta algo.

No creo que esté manteniendo la funcionalidad predeterminada de setAttr

Aquí hay un ejemplo:

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] # 

!

Esto funcionó para mí:

 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) 

Sin embargo, no estoy seguro de que esta sea una buena ruta para ir.

Si las operaciones externas llevan mucho tiempo (por ejemplo, está utilizando esto para disfrazar el acceso a una base de datos o un archivo de configuración) puede dar a los usuarios del código una impresión errónea sobre el costo. En un caso así, debe usar un método para que los usuarios entiendan que están iniciando una acción, no solo observando los datos.

OTOH si el acceso es rápido, tenga cuidado de que la encapsulación de sus clases no se rompa. Si está haciendo esto para obtener datos de la escena maya (estilo pymel, o como en este ejemplo ) no es un gran problema, ya que los costos de tiempo y la estabilidad de los datos están más o menos garantizados. Sin embargo, querría evitar el escenario en el código de ejemplo que publicó: sería muy fácil suponer que al establecer ‘translateX’ en un valor determinado se mantendría en un lugar, donde de hecho hay muchas formas en que el contenido de Las variables externas podrían ensuciarse, evitando que puedas conocer tus invariantes mientras usas la clase. Si la clase está destinada a un uso desechable (por ejemplo, su syntax de azúcar para una gran cantidad de procesamiento repetitivo rápido dentro del bucle donde no se ejecutan otras operaciones), podría salirse con la suya, pero de lo contrario, internalice los datos en sus instancias.

Un último problema: si tiene ‘muchas clases’, también tendrá que hacer muchas repeticiones para que esto funcione. Si está tratando de envolver los datos de la escena Maya, lea los descriptores ( aquí hay un gran video de 5 minutos ). Puede ajustar las propiedades de transformación típicas, por ejemplo, como esto:

 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') 

En código real, necesitarías un manejo de errores y una configuración más limpia, pero ves la idea.

El ejemplo vinculado anteriormente habla sobre el uso de metaclases para completar las clases cuando tiene que configurar muchos descriptores de propiedades, es una buena ruta si no quiere preocuparse por todo el contenido (aunque sí tiene una penalización de tiempo de inicio menor). – Creo que esa es una de las razones del notorio rastreo de inicio de Pymel …)

Decidí ir con @theodox s y usar descriptores Esto parece funcionar bien:

 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 

Como nota al margen…
Resulta que mi código original habría funcionado simplemente cambiando el atributo get con getattr

no es muy necesario

¿Por qué no hacer lo mismo en __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) 

Código de prueba

 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'] 

Salida:

 Getting ---> translateX 1.0 Setting ---> translateX Getting ---> translateX 9999 Setting ---> name Getting ---> name myName Getting ---> __dict__ myName 

Aquí en tu fragmento:

 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 

Vea, en su __setattr__() cuando llamó a myInstance.name = 'myName' , el name no está en las attrKeys de attrKeys , por lo que no se inserta en el diccionario de datos externos sino que se agrega en self.__dict__['name'] = value

Por lo tanto, cuando intenta buscar ese nombre en particular, no ingresa en su diccionario de datos externalData por lo que su __getattribute__ se __getattribute__ con una excepción.

Puede arreglar eso cambiando el __getattribute__ lugar de generar un cambio de excepción como se muestra a continuación:

 def __getattribute__(self, name): print 'Getting --->', name if name in attrKeys: return externalData[name] else: return object.__getattribute__(self, name)