仅静态方法 class 和 Python 中的子 classes - 是否有更好的设计模式?

Static method-only class and subclasses in Python - is there a better design pattern?

我正在为金融工具定价,每个金融工具对象都需要一个日计数器作为 属性。有 4 种日计数器,它们的两种方法 year_fractionday_count 中的每一种都有不同的实现。金融工具上的这个天数计数器 属性 在其他 classes 定价时使用,以了解如何适当地贴现曲线等。但是,所有的天数计算方法都是静态的,无非就是应用一些公式。

因此,尽管我在网上读到的所有内容都告诉我不要使用静态方法,而只使用模块级函数,但我无法找到一种方法来很好地传递正确的 DayCounter 而不是实现这样的东西

class DayCounter:
    __metaclass__ = abc.ABCMeta

    @abc.abstractstaticmethod
    def year_fraction(start_date, end_date):
        raise NotImplementedError("DayCounter subclass must define a year_fraction method to be valid.")

    @abc.abstractstaticmethod
    def day_count(start_date, end_date):
        raise NotImplementedError("DayCounter subclass must define a day_count method to be valid.")


class Actual360(DayCounter):

    @staticmethod
    def day_count(start_date, end_date):
        # some unique formula

    @staticmethod
    def year_fraction(start_date, end_date):
        # some unique formula


class Actual365(DayCounter):

    @staticmethod
    def day_count(start_date, end_date):
        # some unique formula

    @staticmethod
    def year_fraction(start_date, end_date):
        # some unique formula


class Thirty360(DayCounter):

    @staticmethod
    def day_count(start_date, end_date):
        # some unique formula

    @staticmethod
    def year_fraction(start_date, end_date):
        # some unique formula

class ActualActual(DayCounter):

    @staticmethod
    def day_count(start_date, end_date):
        # some unique formula

    @staticmethod
    def year_fraction(start_date, end_date):
        # some unique formula

因此,在将工具作为参数传递的某些特定工具的定价引擎中,我可以根据需要使用工具的日计数器 属性。

我是否遗漏了 Python 中更惯用的/在风格上可以接受的东西,或者这似乎适合仅用于静态方法的 classes?


示例

我有一个 class FxPricingEngine,它有一个 __init__ 方法传递给 FxInstrument 和随后的 underlying_instrument 属性。然后,为了使用我的定价引擎的 Value 方法,我需要使用特定日期计数器对曲线进行折扣。我有一个 YieldCurve class 和一个 discount 方法,我将 self.underlying_instrument.day_counter.year_fraction 传递给该方法,这样我就可以应用正确的公式。实际上,classes 所做的只是为独特的实现提供一些逻辑组织。

坦率地说,以这种方式定义静态方法没有多大意义。在您的情况下,静态方法的唯一目的是为您的函数名称提供命名空间,即您可以像 Actual365.day_count 一样调用您的方法,从而更清楚地表明 day_count 属于 Actual365 功能。

但是您可以通过定义一个名为 actual365.

的模块来实现同样的想法
import actual365
actual365.day_count()

就面向对象而言,您的代码没有提供 OOP 设计所提供的任何优势。您刚刚将函数包装在 class.

现在我注意到你所有的方法都使用 start_date 和 end_date,如何将它们用作实例变量。

class Actual365(object):

    def __init__(self, start_date, end_date):
        self.start_date, self.end_date = start_date, end_date

    def day_count(self):
        # your unique formula

    ...

此外 AbstractClasses 在像 Python 这样的 Duck-Typed 语言中没有多大意义。只要某个对象提供了一种行为,它就不需要从一些抽象 class.

继承

如果这对您不起作用,则仅使用函数可能是更好的方法。

没有

据我所知没有。我认为您的设计模式还可以。特别是如果您的代码有可能增长到每个 class 超过 2 个函数,并且 class 中的函数是连接的。

如果可以使用任何函数组合,请使用函数传递 方法。 modules 方法和您的方法非常相似。模块的优点或缺点(视情况而定)是您的代码被分成许多文件。您的方法允许您使用 isinstance,但您可能不需要它。

直接传递函数

如果你只有一个函数,你可以直接传递这个函数而不是使用 class。但是一旦你有 2 个或更多函数 和不同的实现,classes 对我来说似乎很好。只需添加一个文档字符串来解释用法。我假设一个 class 中的两个函数实现有些关联。

使用模块

您可以使用模块而不是 classes(例如模块 Actual365DayCounter 和模块 Actual360DayCounter)并使用类似 if something: import Actual360DayCounter as daycounterelse: import Actual365ayCounter as daycounter.

的东西

或者您可以导入所有模块并将它们放入字典中(感谢@freakish 的评论),例如 MODULES = { 'Actual360': Actual360, ... } 并简单地使用 MODULES[my_var].

但我怀疑这是否是一种更好的设计模式,因为您会将源代码拆分成许多小模块。

另一个选项:

一种方法是只使用一个 class:

class DayCounter:
    def __init__(self, daysOfTheYear = 360):
        self.days_of_the_year = daysOfTheYear

并使用 self.days_of_the_year 创建函数。但这仅在 days_of_the_year 实际上是函数的参数时才有效。如果您必须在函数实现中使用大量 if ... elif .. elif ...,这种方法会更糟

实际上,面向对象在您的场景中没有任何意义:没有与您的类型实例关联的数据,因此某种类型的任何两个对象(例如 Thirty360)将相等(即你只有单身人士)。

您似乎希望能够根据行为对客户端代码进行参数化 - 您的方法所操作的数据不是在构造函数中给出的,而是通过方法的参数给出的。在那种情况下,简单的自由函数可能是更直接的解决方案。

例如,给定一些在您的柜台上运行的假想客户端代码,例如:

def f(counter):
  counter.day_count(a, b)
  # ...
  counter.year_fraction(x, y)

...你也可以想象立即传递两个函数作为参数而不是一个对象,例如有

def f(day_count, year_fraction):
    day_count(a, b)
    # ...
    year_fraction(x, y)

在调用方,您将传递普通函数,例如

f(thirty360_day_count, thirty360_year_fraction)

如果您愿意,您也可以为函数使用不同的名称,或者您可以在单独的模块中定义它们以获得命名空间。您还可以轻松地传递特殊功能,例如 way(例如,如果您只需要 day_count 是正确的,但 year_fraction 可能是空话)。