Как избежать циклических зависимостей при настройке свойств?

Это принципиальный вопрос для классов, занимающихся математическими/физическими уравнениями, где пользователю разрешено устанавливать любой параметр, на основе которого рассчитываются остальные. В этом примере я хотел бы также установить частоту, избегая циклических зависимостей.

Например:

from traits.api import HasTraits, Float, Property
from scipy.constants import c, h
class Photon(HasTraits):
    wavelength = Float # would like to do Property, but that would be circular?
    frequency = Property(depends_on = 'wavelength')
    energy = Property(depends_on = ['wavelength, frequency'])
    def _get_frequency(self):
        return c/self.wavelength
    def _get_energy(self):
        return h*self.frequency

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

  1. Длина волны меняется
  2. Это запускает обновление обоих зависимых объектов: частоты и энергии.
  3. Но энергия нуждается в обновлении частоты, чтобы энергия имела значение, соответствующее новой длине волны!

(Ответ, который будет принят, должен также решить эту потенциальную проблему синхронизации.)

Итак, какой шаблон проектирования лучше всего подходит для решения этих взаимозависимых проблем? В конце я хочу, чтобы пользователь мог обновлять либо длину волны, либо частоту, а частота/длина волны и энергия должны обновляться соответственно.

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

Да начнется соревнование! ;)


person K.-Michael Aye    schedule 30.03.2012    source источник
comment
Я думаю, что traits.api может быть излишним для ваших нужд. Есть ли какая-то причина, по которой стандартные свойства проблемы не будут работать для вас? Черты в основном предназначены для настройки межклассовых, а не внутриклассовых зависимостей из того, что я вижу на их странице; вы делаете внутриклассовые зависимости.   -  person Silas Ray    schedule 31.03.2012
comment
Я не согласен, так как внутриклассовые зависимости очень полезны для разработки графического интерфейса пользователя с соответствующей библиотекой traits.ui, которая построена на чертах.   -  person K.-Michael Aye    schedule 31.03.2012
comment
то есть мини-GUI или мини-приложения для решения некоторых задач расчета/анализа данных. Насколько хорошо они работают для крупномасштабных приложений с графическим интерфейсом, я не знаю, но, по крайней мере, для быстрой разработки приложений, когда элементы графического интерфейса запускают какие-то вещи, а другие вещи автоматически обновляются, эти свойства гениальны.   -  person K.-Michael Aye    schedule 31.03.2012
comment
Но если ваши значения получены с самого начала, стандартные свойства задач будут работать нормально. У вас есть 1 фундаментальное значение, затем ваши 2 константы. Вы заставляете свои геттеры вычислять все от фундаментального значения, а затем все ваши сеттеры переводят в фундаментальное значение. Все, что вы получаете от использования traits.api (а у вас его еще нет), — это способ по существу кэшировать ваши результаты, что в любом случае является преждевременной оптимизацией.   -  person Silas Ray    schedule 02.04.2012


Ответы (2)


Благодаря Адаму Хьюзу и Уоррену Векессеру из списка рассылки Enthought я понял, чего мне не хватало в моем понимании. Свойства на самом деле не существуют как атрибут. Теперь я смотрю на них как на что-то вроде «виртуального» атрибута, который полностью зависит от того, что делает создатель класса во время вызова _getter или _setter.

Поэтому, когда я хотел бы иметь возможность устанавливать длину волны И частоту пользователем, мне нужно только понять, что сама частота не существует как атрибут и что вместо этого во время _setting частоты мне нужно обновить «фундаментальную» длину волны атрибута , так что в следующий раз, когда потребуется частота, она снова будет рассчитана с новой длиной волны!

Также нужно поблагодарить пользователя sr2222, который заставил меня задуматься об отсутствующем кешировании. Я понял, что зависимости, которые я установил с помощью ключевого слова «depends_on», требуются только при использовании черты «cached_property». Если стоимость вычислений не так высока или они выполняются не так часто, _getters и _setters позаботятся обо всем, что вам нужно, и вам не нужно использовать ключевое слово «depends_on».

Вот теперь оптимизированное решение, которое я искал, которое позволяет мне устанавливать длину волны или частоту без циклических петель:

class Photon(HasTraits):
    wavelength = Float 
    frequency = Property
    energy = Property

    def _wavelength_default(self):
        return 1.0
    def _get_frequency(self):
        return c/self.wavelength
    def _set_frequency(self, freq):
        self.wavelength = c/freq
    def _get_energy(self):
        return h*self.frequency

Можно было бы использовать этот класс следующим образом:

photon = Photon(wavelength = 1064)

or

photon = Photon(frequency = 300e6)

чтобы установить начальные значения и получить энергию сейчас, нужно просто использовать ее напрямую:

print(photon.energy)

Обратите внимание, что метод _wavelength_default позаботится о случае, когда пользователь инициализирует экземпляр Photon без указания начального значения. Только при первом доступе к длине волны этот метод будет использоваться для ее определения. Если бы я этого не сделал, первый доступ к частоте привел бы к вычислению 1/0.

person K.-Michael Aye    schedule 07.04.2012

Я бы рекомендовал научить ваше приложение тому, что может быть получено из чего. Например, типичным случаем является то, что у вас есть набор из n переменных, и любая из них может быть получена из остальных. (Конечно, вы можете моделировать и более сложные случаи, но я бы не стал этого делать, пока вы не столкнетесь с такими случаями).

Это можно смоделировать следующим образом:

# variable_derivations is a dictionary: variable_id -> function
# each function produces this variable's value given all the other variables as kwargs
class SimpleDependency:
  _registry = {}
  def __init__(self, variable_derivations):
    unknown_variable_ids = variable_derivations.keys() - self._registry.keys():
      raise UnknownVariable(next(iter(unknown_variable_ids)))
    self.variable_derivations = variable_derivations

  def register_variable(self, variable, variable_id):
    if variable_id in self._registry:
      raise DuplicateVariable(variable_id)
    self._registry[variable_id] = variable

  def update(self, updated_variable_id, new_value):
    if updated_variable_id not in self.variable_ids:
      raise UnknownVariable(updated_variable_id)
    self._registry[updated_variable_id].assign(new_value)
    other_variable_ids = self.variable_ids.keys() - {updated_variable_id}
    for variable_id in other_variable_ids:
      function = self.variable_derivations[variable_id]
      arguments = {var_id : self._registry[var_id] for var_id in other_variable_ids}
      self._registry[variable_id].assign(function(**arguments))

class FloatVariable(numbers.Real):
  def __init__(self, variable_id, variable_value = 0):
    self.variable_id = variable_id
    self.value = variable_value
  def assign(self, value):
    self.value = value
  def __float__(self):
    return self.value

Это просто набросок, я не тестировал и не продумывал все возможные проблемы.

person max    schedule 04.04.2012