HavelTheGreat
HavelTheGreat

Reputation: 3386

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

I am pricing financial instruments, and each financial instrument object requires a day counter as a property. There are 4 kinds of day counters which have different implementations for each of their two methods, year_fraction and day_count. This day counter property on financial instruments is used in other classes when pricing, to know how to discount curves appropriately, etc. However, all of the day count methods are static, and doing nothing more than applying some formula.

So despite everything I've read online telling me to not use static methods and just have module-level functions instead, I couldn't see a way to nicely pass around the correct DayCounter without implementing something like this

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

So that in a pricing engine for some particular instrument that is passed an instrument as a parameter, I can use the instrument's day counter property as needed.

Am I missing something more idiomatic / stylistically acceptable in Python or does this seem like appropriate use for static method-only classes?


Example:

I have a class FxPricingEngine, which has an __init__ method passed an FxInstrument and subsequent underlying_instrument property. Then in order to use the Value method of my pricing engine I need to discount a curve with a particular day counter. I have a YieldCurve class with a discount method to which I pass self.underlying_instrument.day_counter.year_fraction such that I can apply the correct formula. Really all that the classes are serving to do is provide some logical organization for the unique implementations.

Upvotes: 4

Views: 1862

Answers (3)

Bernhard
Bernhard

Reputation: 2251

No

Not that I know of. In my opinion your design pattern is ok. Especially if there is the possibility of your code growing to more than 2 functions per class and if the functions inside a class are connected.

If any combination of function is possible, use the function passing approach. The modules approach and your approach are quite similar. The advantage or disadvantage (it depends) of modules is that your code get split up into many files. Your approach allows you to use isinstance, but you probably won't need that.

Passing functions directly

If you had only one function, you could just pass this function directly instead of using a class. But as soon as you have 2 or more functions with different implementations, classes seem fine to me. Just add a docstring to explain the usage. I assume that the two function implementation in one class are somewhat connected.

Using modules

You could use modules instead of classes (e.g. a module Actual365DayCounter and a module Actual360DayCounter) and use something like if something: import Actual360DayCounter as daycounter and else: import Actual365ayCounter as daycounter.

Or you could import all modules and put them in a dictionary (thanks to the comment by @freakish) like MODULES = { 'Actual360': Actual360, ... } and simply use MODULES[my_var].

But I doubt this would be a better design pattern, as you would split up your source coude in many tiny modules.

Another option:

One way would be to use only one class:

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

And make the functions using self.days_of_the_year. But this only works if days_of_the_year is actually a parameter of the function. If you would have to use a lot of if ... elif .. elif ... in your function implementation, this approach would be worse

Upvotes: -1

Frerich Raabe
Frerich Raabe

Reputation: 94549

As it is, object orientation does not make any sense in your scenario: there is no data associated with an instance of your types, so any two objects of some type (e.g. Thirty360) are going to be equal (i.e. you only have singletons).

It looks like you want to be able to parametrise client code on behaviour - the data which your methods operate on is not given in a constructor but rather via the arguments of the methods. In that case, plain free functions may be a much more straight forward solution.

For instance, given some imaginary client code which operates on your counters like:

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

...you could just as well imagine passing two functions as arguments right away instead of an object, e.g. have

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

On the caller side, you would pass plain functions, e.g.

f(thirty360_day_count, thirty360_year_fraction)

If you like, you could also have different names for the functions, or you could define them in separate modules to get the namespacing. You could also easily pass special functions like way (for instance if you only need the day_count to be correct but the year_fraction could be a noop).

Upvotes: 4

hspandher
hspandher

Reputation: 16763

Well to be frank it doesn't make much sense in defining static methods this way. The only purpose static methods are serving in your case is providing a namespace to your function names i.e. you can call your method like Actual365.day_count making it more clear that day_count belongs to Actual365 functionality.

But you can accomplish the same think by defining a module named actual365.

import actual365
actual365.day_count()

As far as Object Orientation is concerned, your code is not offering any advantage that OOP design offers. You have just wrapped your functions in a class.

Now I noticed all your methods are using start_date and end_date, how about using them as instance variables.

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

    ...

Besides AbstractClasses don't make much sense in Duck-Typed languages like Python. As long as some object is providing a behavior it doesn't need to inherit from some abstract class.

If that doesn't work for you, using just functions might be better approach.

Upvotes: -1

Related Questions