Есть ли простой способ уведомлять другие поля всякий раз, когда поле @property было изменено
Я пытаюсь определить класс, интегрированный с несколькими видами выражения цветового пространства.
т.е.
#'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 ответ
Ваше свойство возвращает объект списка, и этот объект списка по существу является отдельным. Что касается свойства, вы читаете только этот объект списка.
Ваше расширенное заявление о назначении:
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]
изменено, что может сделать операции синхронизации более эффективными.