【问题标题】:How to avoid circular dependencies when setting Properties?设置属性时如何避免循环依赖?
【发布时间】:2012-04-14 14:19:27
【问题描述】:

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

例如:

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. 但是能量需要更新频率,以便能量具有适合新波长的值!

(被接受的答案也应该解决这个潜在的时间问题。)

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

这类问题当然会出现在几乎所有试图处理方程式的课程中。

让比赛开始吧! ;)

【问题讨论】:

  • 我认为traits.api 可能对您的需求来说太过分了。是否有任何理由标准问题属性不适合您? Traits 主要用于设置类间的依赖关系,而不是我在他们页面上看到的类内依赖关系;你正在做类内依赖。
  • 我不同意,因为类内依赖对于设计 GUI 非常有帮助,相关的 traits.ui 库是建立在特征之上的。
  • 即用于解决一些计算/数据分析任务的迷你图形用户界面或迷你应用程序。这些对于大型 GUI 应用程序的效果如何,我不知道,但至少对于 GUI 元素触发内容和其他内容自动更新的快速应用程序开发,这些属性非常巧妙。
  • 但是,如果您的值是从一开始就派生出来的,那么标准问题属性就可以正常工作。您有 1 个基本价值,然后是 2 个常数。你让你的属性 getter 都从基本值计算,然后你的 setter 都转换为基本值。使用 traits.api(你甚至还没有)所得到的只是一种基本上缓存结果的方法,无论如何这都是过早的优化。

标签: python traits enthought


【解决方案1】:

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

因此,当我希望能够由用户设置波长和频率时,我只需要了解频率本身并不作为属性存在,而是在频率的 _setting 时间我需要更新'fundamental '属性波长,这样下次需要频率时,用新的波长重新计算!

我还需要感谢用户 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)

photon = Photon(frequency = 300e6)

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

print(photon.energy)

请注意,_wavelength_default 方法会处理用户在未提供初始值的情况下初始化 Photon 实例的情况。仅对于波长的第一次访问,该方法将用于确定它。如果我不这样做,第一次访问频率将导致 1/0 计算。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2013-02-06
      • 1970-01-01
      • 2018-09-03
      • 2012-02-15
      • 2013-04-02
      • 1970-01-01
      • 2011-03-20
      • 2012-08-10
      • 1970-01-01
      相关资源
      最近更新 更多