这是处理数学/物理方程的类的设计原则问题,其中允许用户设置正在计算剩余部分的任何参数。 在这个例子中,我希望能够设置频率,同时避免循环依赖。
例如:
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
我也知道这里有更新触发时序问题,因为我不知道更新将被触发的顺序:
(接受的答案也应该解决这个潜在的时间问题。)
那么,解决这些相互依赖的问题的最佳设计模式是什么? 最后,我希望用户能够更新波长或频率和频率/波长,并相应地更新能量。
这种问题当然确实出现在基本上所有试图处理方程式的类中。
让比赛开始吧! ;)
答案 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
这只是一个草图,我没有测试或思考每一个可能的问题。