【问题标题】:Creating Python class with mutually exclusive arguments using attrs使用 attrs 创建具有互斥参数的 Python 类
【发布时间】:2021-11-21 04:43:03
【问题描述】:

我有一个具有两个互斥参数(pricesreturns)的类。也就是说,不能同时提供它们来实例化一个对象。

但是,该类需要两者进行内部计算。所以我想从用户提供的pd.Series 中计算出缺失的pd.Series

我创建了两个替代类构造函数(from_pricesfrom_returns)。使用这些构造函数,该类将被正确实例化。

这是代码。它利用了attrs 库 (www.attrs.org)。

import pandas as pd

import attr


@attr.s
class MutuallyExclusive:
    prices: pd.Series = attr.ib()
    returns: pd.Series = attr.ib()
    trading_days_per_year: int = attr.ib(default=252)

    @classmethod
    def from_prices(cls, price_series: pd.Series, trading_days: int = 252):
        return cls(
            price_series,
            price_series.pct_change(),
            trading_days,
        )

    @classmethod
    def from_returns(cls, return_series: pd.Series):
        return cls(
            pd.Series(data=100 + 100 * (returns.add(1).cumprod() - 1)),
            return_series,
        )


if __name__ == "__main__":
    prices = pd.Series(data=[100, 101, 98, 104, 102, 108])
    returns = pd.Series(data=[0.01, 0.03, -0.02, 0.01, -0.03, 0.04])

    obj_returns = MutuallyExclusive.from_returns(returns)
    obj_prices = MutuallyExclusive.from_prices(prices, trading_days=100)

但是,用户仍然可以拨打obj = MutuallyExclusive(prices, returns),尽管这两个系列彼此不兼容。捕捉这种情况并引发错误的最佳方法是什么?

编辑:
是否可以一起“禁用”常规构造函数?如果可以仅通过替代构造函数来实例化对象,这将解决问题,不是吗?

【问题讨论】:

    标签: python class mutual-exclusion python-attrs


    【解决方案1】:

    attrs 库是正确的工具吗? 为什么不使用常规的 python 类并自己定义__init__()

    import pandas as pd
    
    
    class MutuallyExclusive:
        def __init__(self, prices: pd.Series = None, returns: pd.Series = None):
           if prices is not None and returns is not None:
              raise ValueError("prices and returns are mutually exclusive")
           self.prices = prices if prices is not None else pd.Series(data=100 * (1 + returns))
           self.returns = returns if returns is not None else prices.pct_change()
    
    if __name__ == "__main__":
        prices = pd.Series(data=[100, 101, 98, 104, 102, 108])
        returns = pd.Series(data=[0.01, 0.03, -0.02, 0.01, -0.03, 0.04])
    
        obj_returns = MutuallyExclusive(returns=returns)
        obj_prices = MutuallyExclusive(prices=prices)
    

    编辑:您更新了您的示例,所以我的答案缺少trading_days_per_year,但概念是相同的。

    如果您想使用attrs 库,其他人指出您可以将您的逻辑放在__attrs_post_init__ 函数中,请参阅下面的示例,无需类方法 请注意,您需要将价格和退货都默认为None

    def __attrs_post_init__(self):
        if self.prices is not None and self.returns is not None:
             raise ValueError("prices and returns are mutually exclusive")
        if self.returns is None:
           self.returns = self.price_series.pct_change()
        if self.prices is None:
           self.prices = pd.Series(data=100 + 100 * (self.returns.add(1).cumprod() - 1))
    

    【讨论】:

    • 当使用常规的 Python 类时,你的答案很清楚。我喜欢attrs 的地方在于我可以设置attr.ib(init=False)。在我的真实案例中,我确实有很多类属性,我通常会使用None 进行初始化。然而,我总是在这方面有点挣扎。比较stackoverflow.com/questions/55800218/…
    【解决方案2】:

    我不知道是否有更惯用的模式,但你可以用布尔锁保护构造函数,检查__attrs_post_init__ 中的锁以防止直接调用构造函数:

    import pandas as pd
    
    import attr
    
    @attr.s
    class MutuallyExclusive:
        prices: pd.Series = attr.ib()
        returns: pd.Series = attr.ib()
    
        def __attrs_post_init__(self):
            if not MutuallyExclusive.constructor_unlocked:
                raise TypeError('Please use the `from_prices` or `from_returns` constructor methods')
    
        @classmethod
        def from_prices(cls, price_series: pd.Series):
            cls.constructor_unlocked = True
            value = cls(price_series, price_series.pct_change())
            cls.constructor_unlocked = False
            return value
    
        @classmethod
        def from_returns(cls, return_series: pd.Series):
            cls.constructor_unlocked = True
            value = cls(pd.Series(data=100 * (1 + return_series)), return_series)
            cls.constructor_unlocked = False
            return value
    
    
    MutuallyExclusive.constructor_unlocked = False
    
    if __name__ == "__main__":
        prices = pd.Series(data=[100, 101, 98, 104, 102, 108])
        returns = pd.Series(data=[0.01, 0.03, -0.02, 0.01, -0.03, 0.04])
    
        obj_returns = MutuallyExclusive.from_returns(returns)
        obj_prices = MutuallyExclusive.from_prices(prices)
    
        bad = MutuallyExclusive(obj_prices.prices, obj_prices.returns)
    

    或者,如果您愿意,threading.Lock:

    import pandas as pd
    
    import attr
    from threading import Lock
    
    mutually_exclusive_constructor_lock = Lock()
    
    @attr.s
    class MutuallyExclusive:
        prices: pd.Series = attr.ib()
        returns: pd.Series = attr.ib()
    
        def __attrs_post_init__(self):
            if not mutually_exclusive_constructor_lock.locked():
                raise TypeError('Please use the `from_prices` or `from_returns` constructor methods')
    
        @classmethod
        def from_prices(cls, price_series: pd.Series):
            with mutually_exclusive_constructor_lock:
                return cls(price_series, price_series.pct_change())
    
        @classmethod
        def from_returns(cls, return_series: pd.Series):
            with mutually_exclusive_constructor_lock:
                return cls(pd.Series(data=100 * (1 + return_series)), return_series)
    
    if __name__ == "__main__":
        prices = pd.Series(data=[100, 101, 98, 104, 102, 108])
        returns = pd.Series(data=[0.01, 0.03, -0.02, 0.01, -0.03, 0.04])
    
        obj_returns = MutuallyExclusive.from_returns(returns)
        obj_prices = MutuallyExclusive.from_prices(prices)
    
        bad = MutuallyExclusive(obj_prices.prices, obj_prices.returns)
    

    【讨论】:

      【解决方案3】:

      您在__attrs_post_init__: https://www.attrs.org/en/stable/init.html#post-init 中查看它

      【讨论】:

      • 您能详细说明一下吗?您的意思是检查pricesreturns 之间的兼容性吗?
      • 正如其他人在上面写的(我正在通过电话接听):您可以使用 None 作为默认值,然后验证其中一个为 None 和一个不是的不变性。
      猜你喜欢
      • 2018-07-25
      • 1970-01-01
      • 2020-07-29
      • 2020-07-14
      • 2016-05-04
      • 1970-01-01
      • 2012-12-04
      • 2013-12-28
      • 2017-01-03
      相关资源
      最近更新 更多