Reputation: 3386
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
Reputation: 2251
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.
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.
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.
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
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
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