Есть ли простой способ уведомлять другие поля всякий раз, когда поле @property было изменено - PullRequest
0 голосов
/ 26 апреля 2019

Я пытаюсь определить класс, интегрированный с несколькими видами выражения цветового пространства.

т.е.,

#'hsv', 'rgb'...such fields are different color space expression of one thing.

my_color = MyColor('#ffffff')
my_color.rgb   #(1.0, 1.0, 1.0)
my_color.rgb24 #(255, 255, 255)
my_color.hsv   #(0.0. 0.0, 1.0)

И я надеюсь, что для некоторых целей этот класс может поддерживать некоторые базовые математические операции,

my_color.hsv[2] -= 1.0

Конечно, все поля должны синхронизировать свое значение при изменении любого другого «связанного» поля.

#after operation above.
my_color.rgb   #(0.0, 0.0, 0.0)
my_color.rgb24 #(0.0, 0.0, 0.0)
my_color.hsv   #(0.0, 0.0, 0.0)

Какой лучший способ сделать это.

Я пробовал аннотации @property и @setter.

@property
def hsv(self):
    return self._hsv

@hsv.setter
def hsv(self, val):
    self._hsv = val
    _sync_change()  #update other fields by the new hsv value

Надеюсь, @setter поможет мне обновить другие поля на основе нового значения 'hsv'. Когда я выполняю операцию присваивания my_color.hsv, она работает.

my_color.hsv = (0.5, 0.5, 0.5)
print(my_color.rgb, my_color.rgb24) #shows correct value.

Однако, когда я беру модификацию не на кортеже, а на элементе кортежа, все становится не так.

my_color.hsv[2] -= 1.0
print(my_color.rgb, my_color.rgb24) #nothing changed.
print(my_color.hsv)  #nothing changed.

Интересно, что происходит, когда я ошибся, и что я могу сделать с помощью @setter, или есть лучший и естественный способ сделать это.

1 Ответ

0 голосов
/ 26 апреля 2019

Ваше свойство возвращает объект списка, и этот объект списка по существу является отдельным. Что касается свойства, вы только читаете этот объект списка.

Ваше расширенное заявление о назначении:

my_color.hsv[2] -= 1.0

в противном случае не устанавливает это свойство. Устанавливает индекс внутри списка. Следующее примерно эквивалентно:

_hsv_list = my_color.hsv
_hsv_list[2] = _hsv_list[2].__isub__(1.0)  # __isub__ is called to handle -=

Там нет my_color.hsv = _hsv_list, поэтому ваш сеттер никогда не вызывается.

Таким образом, хитрость заключается в том, чтобы вернуть пользовательский объект, который позволяет перехватывать __setitem__ и другие обращения:

from collections.abc import Sequence

class HSVValues(Sequence):
    _parent: MyColor

    def __init__(self, parent):
        self._parent = parent

    def __len__(self):
        return len(self._parent._hsv)

    def __getitem__(self, item):
        return self._parent._hsv[item]

    def __setitem__(self, item, value):
        self._parent._hsv[item] = value
        self._parent._sync_change()

и вернуть его из вашей собственности:

@property
def hsv(self):
    return HSVValues(self)

Так что теперь my_color.hsv возвращает не значение _hsv, а экземпляр вышеуказанного класса.

Если этот объект заменит исходный список, _hsv_list[2] = _hsv_list[2].__isub__(1.0) вызовет __setitem__, который сначала обновит self._parent._hsv[2], а затем вызовет метод _sync_change() в родительском классе, чтобы он обработал все остальные значения.

Вы, вероятно, хотите, чтобы _sync_changes принял дополнительную информацию о том, что изменилось; если он знает, что _hsv[2] изменилось, это может сделать операции синхронизации более эффективными.

...