2

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

但是,该类需要两者进行内部计算。所以我想计算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)但是,即使这两个系列彼此不兼容,用户仍然可以调用。捕捉这种情况并引发错误的最佳方法是什么?

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

4

3 回答 3

2

图书馆是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))
于 2021-09-29T12:06:18.563 回答
0

我不知道是否有更惯用的模式,但您可以使用布尔锁保护构造函数,检查锁__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)
于 2021-09-29T12:16:24.730 回答
-1

您检查__attrs_post_init__https ://www.attrs.org/en/stable/init.html#post-init

于 2021-09-29T10:39:54.980 回答