【问题标题】:Class instance attributes derived from other instance attributes从其他实例属性派生的类实例属性
【发布时间】:2020-03-28 14:11:39
【问题描述】:

如果标题含糊不清,我深表歉意,我想不出用一句话来描述我的问题。我正在 python2.7 中构建一些代码,如下所述。

最小的工作示例

我的代码有一个Parameter 类,它实现了namevalue 等属性,看起来像这样。

class Parameter(object):
    def __init__(self, name, value=None, error=None, dist=None, prior=None):
        self.name   = name
        self._value = value # given value for parameter, this is going to be changed very often in an MCMC sampler
        self.error  = error # initial estimate of error for the parameter, will only be set once
        self._dist  = dist # a distribution for the parameter, will only be set once
        self.prior  = prior

    @property
    def value(self):
        return self._value

    @property
    def dist(self):
        return self._dist

如果给定分布,该类还有几个属性可以返回Parameter.dist 的平均值、中位数等。

我还有一门课,例如ParameterSample,它创建了一组不同的 Parameter 对象。其中一些Parameter 对象的属性(例如valueerror)使用Parameter.set_parameter() 函数设置,但其他一些Parameter 对象没有显式设置,但它们的valuedist 属性依赖设置的其他一些Parameter对象:

class ParameterSample(object):
    def __init__(self):
        varied_parameters  = ('a', 'b') # parameter names whose `value` attribute is varied
        derived_parameters = ('c',) # parameter names whose `value` attribute is varied, but depends on `a.value` and `b.value`
        parameter_names    = varied_parameters + derived_parameters
        
        # create `Parameter` objects for each parameter name
        for name in parameter_names:
            setattr(self, name, Parameter(name))

    def set_parameter(self, name, **kwargs):
        for key, val in kwargs.items():
            if key == 'value':
                key = '_'.join(['', key]) # add underscore to set `Parameter._value`
            setattr(getattr(self, name), key, val) # basically does e.g. `self.a.value = 1`

我现在可以创建一个ParameterSample 并像这样使用它们:

parobj = ParameterSample()
parobj.set_parameter('a', value=1, error=0.1)
parobj.set_parameter('b', value=2, error=0.5)

parobj.a.value
>>> 1
parobj.b.error
>>> 0.5

parobj.set_parameter('b', value=3)
parobj.b.value
>>> 3
parobj.b.error
>>> 0.5

我想要什么

我最终想要的是以同样的方式使用Parameter.c。例如:

parobj.c.value
>>> 4 # returns parobj.a.value + parobj.b.value
parobj.c.dist
>>> None # returns a.dist + b.dist, but since they are not currently set it is None

c 因此需要是一个Parameter 对象,具有与ab 相同的属性,但是它的valuedist 根据a 的当前属性进行更新和b

但是,我还应该提到,我希望能够为参数 c 设置允许的先验范围,例如parobj.set_parameter('c', prior=(0,10)) 在对其值进行任何调用之前 -- 因此 c 需要在创建 ParameterSample 对象时是已定义的 Parameter 对象。

如何在我的 ParameterSample 类中实现这一点?

我尝试过的

我尝试过制作自己的装饰器,但我不确定这是否可行,因为我不完全了解如何使用它们。

我还考虑将@property 添加到c,每次调用它都会创建一个新的Parameter 对象,但我觉得这不是可行的方法,因为它可能会减慢代码速度。

我还应该注意,上面的ParameterSample 类将在不同的类中继承,所以无论解决方案是什么,它都应该能够在此设置中使用:

class Companion(ParameterSample)
    def __init__(self, name):
        self.name = name
        super(Companion, self).__init__()

comp = Companion(name='Earth')
comp.set_parameter('a', value=1)
comp.set_parameter('b', value=3)
comp.c.value
>>> 4

【问题讨论】:

  • 仅作记录。你真的应该尽快切换到 python 3,因为 python 2 将不被支持。
  • @Error-SyntacticalRemorse 同意,但我必须稍后再做,因为我目前没有能力将我的代码移植到 python 3。
  • c 怎么知道引用ab?有什么d
  • 如果有d怎么办?*
  • 如果我理解你的问题,cParameterSample 的一个属性,与ab 相同,所以它的任何属性都可以在ParameterSample 中访问如self.a.value 等。

标签: python class decorator


【解决方案1】:

我无法让它在 Python 2 中工作 - setattr 调用似乎从未将属性传播到子类(Companion 将没有 c 属性)。

不过,我使用 Python 3 更成功。由于您有两种参数类型(变量与派生),因此 IMO 有两个类来实现行为是有意义的,而不是将它们全部视为一个。

我添加了一个DerivedParameter 类,继承自Parameter,它采用dependents 参数(连同其父类的args/kwargs),但重新定义valuedist 以提供依赖行为:

class DerivedParameter(Parameter):
    def __init__(self, name, dependents, **kwargs):
        self._dependents = dependents
        super().__init__(name, **kwargs)

    @property
    def value(self):
        try:
            return sum(x._value for x in self._dependents if x is not None)
        except TypeError:
            return None

    @property
    def dist(self):
        try:
            return sum(x._dist for x in self._dependents if x is not None)
        except TypeError:
            return None

然后我调整了你的参数对象的添加方式:

class ParameterSample:
    def __init__(self):
        # Store as instance attributes to reference later
        self.varied_params  = ('a', 'b') # parameter names whose `value` attribute is varied
        self.derived_params = ('c',) # parameter names whose `value` attribute is varied, but depends on `a.value` and `b.value`
        # No more combined names

        # create `Parameter` objects for each varied parameter name
        for name in self.varied_params:
            setattr(self, name, Parameter(name))

        # Create `DerivedParameter` objects for each derived parameter
        # Derived parameters depend on all `Parameter` objects. It wasn't
        # clear if this was the desired behavior though.
        params = [v for _, v in self.__dict__.items() if isinstance(v, Parameter)]
        for name in self.derived_params:
            setattr(self, name, DerivedParameter(name, params))

    def set_parameter(self, name, **kwargs):
        for key, val in kwargs.items():
            if key == 'value':
                key = '_'.join(['', key]) # add underscore to set `Parameter._value`
            setattr(getattr(self, name), key, val) # basically does e.g. `self.a.value = 1`

据此,我可以复制您给定的示例所需的行为:

>>> comp = Companion(name='Earth')
>>> comp.set_parameter('a', value=1)
>>> comp.set_parameter('b', value=3)
>>> print(comp.c.value)
>>> print(comp.c.dist)
4
None
>>> comp.set_parameter('c', prior=(0,10))
>>> print(comp.c.prior)
(0, 10)

正如我在 cmets 中指出的那样,上述设计最终导致所有派生参数都使用 all 变化的参数作为它们的依赖项 - 有效地使 c 和潜在的 d 相同。您应该能够通过一些参数/条件相当轻松地解决此问题。

总的来说,我不得不同意@Error - 虽然语法上的悔恨。这是设计类的一种相当复杂的方法,并且最多会使维护变得混乱。我强烈建议您重新考虑您的设计,并尝试找到一个不涉及动态创建此类属性的适应性通用解决方案。

【讨论】:

    猜你喜欢
    • 2021-09-09
    • 2016-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-21
    • 2011-11-24
    • 2015-12-09
    相关资源
    最近更新 更多