I have a script that takes a list of metrics as an input, and then fetches those metrics from the database to perform various operations with them.
My problem is that different clients get different subsets of the metrics, but I don't want to write a new IF block every time we add a new client. So right now, I have a large IF block that calls different functions based on whether the corresponding metric is in the list. What is the most elegant or Pythonic way of handling this?
Setup and function definitions:
clientOne = ['churn','penetration','bounce']
clientTwo = ['engagement','bounce']
def calcChurn(clientId):
churn = cursor.execute(sql to get churn)
[...]
return churn
def calcEngagement(clientId):
engagement = cursor.execute(sql to get engagement)
[...]
return engagement
Imagine three other functions in a similar format, so there is one function that corresponds to each unique metric. Now here is the block of code in the script that takes the list of metrics:
def scriptName(client, clientId):
if churn in client:
churn = calcChurn(clientId)
if engagement in client:
engagement = calcEngagement(clientId)
if penetration in client:
[...]
Generally, you'd create a mapping of names to functions and use that to calculate the stuff you want:
client_action_map = {
'churn': calcChurn,
'engagement': calcEngagement,
...
}
def scriptName(actions, clientId):
results = {}
for action in actions:
results[action] = client_action_map[action](clientId)
return results
You can create a class with static methods and use getattr
to get the correct method. It's similar to what mgilson suggests but you essentially get the dict creation for free:
class Calculators:
@staticmethod
def calcChurn():
print("called calcChurn")
@staticmethod
def calcEngagement():
print("called calcEngagement")
@staticmethod
def calcPenetration():
print("called calcPenetration")
stats = ["churn", "engagement", "penetration", "churn", "churn", "engagement", "undefined"]
def capitalise(str):
return str[0].upper() + str[1:]
for stat in stats:
try:
getattr(Calculators, "calc" + capitalise(stat))()
except AttributeError as e:
print("Unknown statistic: " + stat)
called calcChurn
called calcEngagement
called calcPenetration
called calcChurn
called calcChurn
called calcEngagement
Unknown statistic: undefined
Perhaps it might make sense to encapsulate the required calls inside an object.
If it makes sense for your clients to be object and especially if many clients call the same set of functions to obtain metrics, then you could create a set of Client
sub classes, which call a predefined set of the functions to obtain metrics.
It's a bit heavier than the mapping dict.
''' Stand alone functions for sql commands.
These definitions however dont really do anything.
'''
def calc_churn(clientId):
return 'result for calc_churn'
def calc_engagement(clientId):
return 'result for calc_engagement'
''' Client base object '''
class Client(object):
''' Base object allows list of functions
to be stored in client subclasses'''
def __init__(self, id):
self.id = id
self.metrics = []
self.args = []
def add_metrics(self, metrics, *args):
self.metrics.extend(metrics)
self.args = args
def execute_metrics(self):
return {m.__name__: m(*self.args) for m in self.metrics}
''' Specific sub classes '''
class Client1(Client):
def __init__(self, id):
''' define which methods are called for this class'''
super(Client1, self).__init__(id)
self.add_metrics([calc_churn], id)
class Client2(Client):
def __init__(self, id):
''' define which methods are called for this class'''
super(Client2, self).__init__(id)
self.add_metrics([calc_churn, calc_engagement], id)
''' create client objects and '''
c1 = Client1(1)
c2 = Client2(2)
for client in [c1, c2]:
print client.execute_metrics()
The result you will get from execute_metrics
is a dict
mapping the function name to its results for that client.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.