设置属性时如何避免循环依赖?

时间:2012-03-30 21:39:06

标签: python traits enthought

这是处理数学/物理方程的类的设计原则问题,其中允许用户设置正在计算剩余部分的任何参数。 在这个例子中,我希望能够设置频率,同时避免循环依赖。

例如:

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. 但能量需要更新频率,以便能量具有适合新波长的值!
  4. (接受的答案也应该解决这个潜在的时间问题。)

    那么,解决这些相互依赖的问题的最佳设计模式是什么? 最后,我希望用户能够更新波长或频率和频率/波长,并相应地更新能量。

    这种问题当然确实出现在基本上所有试图处理方程式的类中。

    让比赛开始吧! ;)

2 个答案:

答案 0 :(得分:2)

感谢来自Enthought邮件列表的Adam Hughes和Warren Weckesser,我意识到我的理解中缺少了什么。 属性并不真正作为属性存在。我现在将它们视为“虚拟”属性,完全取决于类的编写者在调用_getter或_setter时所执行的操作。

因此,当我希望能够通过用户设置波长和频率时,我只需要了解频率本身不作为属性存在,而是在设置频率的时间我需要更新'基波'属性波长,以便下次需要频率时,再次用新波长计算!

我还要感谢用户sr2222让我想到了丢失的缓存。我意识到只有在使用'cached_property'特征时才需要使用关键字'depends_on'设置的依赖关系。如果计算成本不高或者没有经常执行,那么_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)

photon = Photon(frequency = 300e6)

设置初始值并立即获取能量,只需直接使用它:

print(photon.energy)

请注意,当用户初始化Photon实例而不提供初始值时,_wavelength_default方法会处理这种情况。只有首次访问波长时,才会使用此方法来确定它。如果我不这样做,首次访问频率将导致1/0计算。

答案 1 :(得分:0)

我建议教你的应用程序可以从什么中得到什么。例如,一个典型的情况是你有一组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

这只是一个草图,我没有测试或思考每一个可能的问题。