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)
Другие вопросы по тегам