Let me illustrate with an example.
Say I want to create a class Returns
that generates the returns of a stock for example. Returns can be arithmetic or logarithmic, and I want to be able to choose at instantiation which I want. For the moment, I do the following:
def arithmetic(prices):
daily_ret = []
for i rows in prices:
returns.append(prices(i)-prices(i-1))
return daily_ret
def logarithmic(prices):
daily_ret = []
for i rows in prices:
returns.append(np.ln(prices(i) / prices(i-1))
return daily_ret
class Returns:
def __init__(self, stock_ticker, return_calc=arithmetic):
self.stock_ticker = stock_ticker
self.return_calc = return_calc
def calculate_returns(self):
get_price_data_from_ticker = whatever_data_source()
if self.return_calc = 'arithmetic':
returns = arithmetic(get_price_data_from_ticker)
elif self.return_calc = 'logarithmic':
returns = logarithmic(get_price_data_from_ticker)
return returns
x = Returns('AAPL US Equity', return_calc='logarithmic')
print(x.calculate_returns())
This seems like the code is just badly written, and I'm missing something obvious because I've had the same question over and over.
Another thing that I've done is use getattr to dynamically get the name of the function or class I'll use and pass on arguments. Also another thing I've found is using globals() to dynamically create classes.
I'm wondering if there's a macro construction that I'm missing, because even if these 2 solutions above feel quite nice, it feels like there's a deeper structural issue with the code.
Language is Python 3
3 Answers 3
In this case, I think using different classes is probably the way to go.
from abc import ABC, abstractmethod
class Returns(ABC):
def __init__(self, stock_ticker):
self.stock_ticker = stock_ticker
def get_price_data_from_ticker(self):
pass # FIXME: implement getting stuff from the data source here
@abstractmethod
def calculate_returns(self, prices):
pass
def get_returns(self):
return self.calculate_returns(self.get_price_data_from_ticker())
class ArithmeticReturns(Returns):
def calculate_returns(self, prices):
return [prices(i) - prices(i - 1) for i in prices]
class LogarithmicReturns(Returns):
def calculate_returns(self, prices):
return [np.ln(prices(i) / prices(i - 1)) for i in prices]
x = LogarithmicReturns('AAPL US Equity')
print(x.get_returns())
Concepts used:
- Abstract Base Classes:
Returns
is now a class that cannot be substantiated, only subclasses that implement all abstract methods can. This means both subclasses implement the same interface without having to do all of the heavy lifting themselves. List comprehensions:
l = [a + 1 for a in exp]
is equivalent tol = [] for a in exp: l.append(a + 1)
- Separation of concerns:
calculate_returns
is now no longer concerned with how data is collected from the ticker, it purely calculates the returns based on the prices given to it.
Your code contained syntax errors in arithmetic
and logarithmic
in the loops, so you will need to fix that. If prices
is a list, I would use something like [current_price - previous_price for current_price, previous_price in zip(prices[1:], prices)]
, but I decided to leave it as is in case prices
is actually a strange iterable that iterates over the indexes and is indexed by calling.
-
1generally speaking, this is Strategy design patternNick Alexeev– Nick Alexeev2018年11月20日 05:27:55 +00:00Commented Nov 20, 2018 at 5:27
-
Isn't a class a little too Java-like for Python? To each language its own, injecting a class instance when all you need is a function seems a little heavy to me!gcali– gcali2019年04月05日 13:43:21 +00:00Commented Apr 5, 2019 at 13:43
I think you're on the right way; but using some dependency injection might work better in your case. You could do something like this:
def arithmetic(prices):
daily_ret = []
for i rows in prices:
returns.append(prices(i)-prices(i-1))
return daily_ret
def logarithmic(prices):
daily_ret = []
for i rows in prices:
returns.append(np.ln(prices(i) / prices(i-1))
return daily_ret
class Returns:
def __init__(self, stock_ticker, return_calc=arithmetic):
self.stock_ticker = stock_ticker
self.return_calc = return_calc
def calculate_returns(self):
get_price_data_from_ticker = whatever_data_source()
return self.return_calc(get_price_data_from_ticker)
x = Returns('AAPL US Equity', return_calc=logarithmic)
print(x.calculate_returns())
by directly passing the function instead of passing a string constant to identify which function to use.
By the way, this is what you were doing in this line:
def __init__(self, stock_ticker, return_calc=arithmetic):
which means that the default value that you are passing right now isn't a string, but directly the function
A simpler method is to include both functions as class methods, but assign the visible method in the constructor, like
self.calculate_return = self.logarithmic if .... else self.arithmetic
Then the caller will always use the proper function without doing a test and a deeper method call each time. You can also do this with subclasses but this is simpler, if a little scary for non-python coders.