Python - повторная реализация __setattr__ с супер
Я знаю, что этот был рассмотрен ранее, и, возможно, это не самый питонический способ создания класса, но у меня есть много разных классов узлов майя с большим количеством @properties для извлечения / установки данных узла, и я хочу увидеть если процедурное построение атрибутов сокращает накладные расходы / обслуживание.
Мне нужно повторно реализовать __setattr__, чтобы поддерживать стандартное поведение, но для определенных специальных атрибутов значение get / set для внешнего объекта.
Я видел примеры повторной реализации __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 ответа
Это сработало для меня:
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)
Однако я не уверен, что это хороший путь.
Если внешние операции занимают много времени (например, вы используете это для маскировки доступа к базе данных или файлу конфигурации), вы можете дать пользователям кода неправильное представление о стоимости. В таком случае вы должны использовать метод, чтобы пользователи понимали, что они инициируют действие, а не просто смотрят на данные.
OTOH, если доступ быстрый, будьте осторожны, чтобы инкапсуляция ваших классов не нарушалась. Если вы делаете это, чтобы получить данные сцены 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')
В реальном коде вам понадобится обработка ошибок и более чистая конфигурация, но вы видите идею.
Приведенный выше пример говорит об использовании метаклассов для заполнения классов, когда у вас есть множество дескрипторов свойств для настройки, это хороший путь, если вы не хотите беспокоиться обо всем шаблоне (хотя он имеет незначительное время запуска - Я думаю, что это одна из причин пресловутого стартапа Pymel.
Я решил использовать @theodox s и использовать дескрипторы. Кажется, это хорошо работает:
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.
супер не нужно
Почему бы не сделать то же самое в __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)