Consider the following Python code snippet where we define a Portfolio
, Company
and Deposit
class. A Portfolio
object simply acts as a union of companies & deposits. And we can run metrics on the portfolio like Profit
. Questions:
For every new metric I include in the Company
or Deposit
class I need to manually add a corresponding function in the Portfolio
class; despite the fact that their behaviour is always the same: sum across all investments. Is there a way to improve this logic/construction of classes? What if we need to add 100 other metrics...
The Deposit
class only has a Profit
function, but not Loss
(interest in a bank account is assumed to be guaranteed). Is there a way to treat "undefined" metrics as always returning 0? Or is there a cleaner/more correct to define these metrics? What if we need to cover 100 different investment types that may or may not have different metrics...
class Company():
def __init__(self, ItemsSold, ItemPrice, Expenses, Fines):
self.ItemsSold = ItemsSold
self.ItemPrice = ItemPrice
self.Expenses = Expenses
self.Fines = Fines
def Profit(self):
return self.ItemsSold * self.ItemPrice
def Loss(self):
return self.Expenses + self.Fines
def ProfitAndLoss(self):
return self.Profit() - self.Loss()
class Portfolio():
def __init__(self, Investments):
self.Investments = Investments
def Profit(self):
return sum([inv.Profit() for inv in self.Investments])
def Loss(self):
return sum([inv.Loss() for inv in self.Investments])
def ProfitAndLoss(self):
return sum([inv.ProfitAndLoss() for inv in self.Investments])
class Deposit():
def __init__(self, Notional, InterestRate, TimeHorizon):
self.Notional = Notional
self.InterestRate = InterestRate
self.TimeHorizon = TimeHorizon
def Profit(self):
return self.Notional * self.InterestRate * self.TimeHorizon
myComp1 = Company(100,2,50,20)
myComp2 = Company(200,2,100,80)
myDepos = Deposit(100,0.02,3)
myPortf = Portfolio([myComp1,myComp2,myDepos])
print(myPortf.Profit()) # Works fine
print(myPortf.ProfitAndLoss()) # Throws an error
The second question is easy: all you have to do is to create a Base
class where each metrics is defined as a method returning 0. Then derive all your Invest
classes (Company
, Deposit
, etc) from the Base
class, so as all undefined metrics will call the corresponding method in the Base
class.
The first question is a bit tougher as it requires some meta-programming. Your Portfolio
class can also be derived from the Base
class, then it looks in the method dictionary of the Base
class (Base.__dict__
) to retrieve all metrics names. Afterwards, for all these metrics, it creates a specific lambda method that calls this metrics for each item in your Investments
list and sums up the results. Here is a skeleton code for this:
class Base(object):
def f1(self):
return 0
def f2(self):
return 0
class InvestA(Base):
def f2(self):
return 2
class InvestB(Base):
def f1(self):
return 1
class Portfolio(Base):
def __init__(self, invest):
self.invest = invest
for name in [n for n in Base.__dict__ if n[:2] != '__']:
self.__dict__[name] = lambda name=name: self.sum(name)
def sum(self, name):
return sum([i.__class__.__dict__[name](i) for i in self.invest
if name in i.__class__.__dict__])
A = InvestA()
print("A.f1 = %s, A.f2 = %s" % (A.f1(), A.f2()))
B = InvestB()
print("B.f1 = %s, B.f2 = %s" % (B.f1(), B.f2()))
P = Portfolio([A,A,B])
print('P.f1 = A.f1 + A.f1 + B.f1 =', P.f1())
print('P.f2 = A.f2 + A.f2 + B.f2 =', P.f2())
which produces the following output:
A.f1 = 0, A.f2 = 2
B.f1 = 1, B.f2 = 0
P.f1 = A.f1 + A.f1 + B.f1 = 1
P.f2 = A.f2 + A.f2 + B.f2 = 4
As you can see, A.f1, B.f2, P.f1 and P.f2 are not explicitely defined as methods, but they can be called thanks to inheritance and meta-programming