2015-05-18 3 views
2

У меня есть сценарий, который принимает список метрик в качестве ввода, а затем извлекает эти показатели из базы данных для выполнения различных операций с ними.Вызов различных функций в Python на основе значений в списке

Моя проблема заключается в том, что разные клиенты получают разные подмножества показателей, но я не хочу писать новый блок IF каждый раз, когда мы добавляем нового клиента. Итак, прямо сейчас у меня есть большой блок IF, который вызывает разные функции на основе того, соответствует ли соответствующая метрика в списке. Что является самым элегантным или Pythonic способ справиться с этим?

настройка и функция определения:

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 три других функций в подобном формате, так что есть одна функции, которая соответствует каждой единственной метрике. Сейчас здесь находится блок кода в скрипте, который принимает список метрик:

def scriptName(client, clientId): 
    if churn in client: 
     churn = calcChurn(clientId) 
    if engagement in client: 
     engagement = calcEngagement(clientId) 
    if penetration in client: 
    [...] 
+3

Если вы беспокоитесь о том, что делаете вещи максимально возможными, возможно, вы также захотите использовать стиль [PEP 8] (https://www.python.org/dev/peps/pep-0008/): ' calc_churn' и 'calc_engagement', пробелы после запятых в списках и т. д. – abarnert

ответ

8

Как правило, вы бы создать отображение имен для функций и использовать, чтобы вычислить вещи вы хотите:

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 
+0

Это сработало отлично - спасибо! Несколько вопросов из любопытства: 1. Почему результаты [действие] хранят новую переменную для каждого элемента в списке, но если я просто использую действие, он сохраняет только одну переменную, которая продолжает переписываться с каждой итерацией цикла? 2. Что мне нужно делать по-другому, если каждая функция в client_action_map принимала различное количество входов? – dsal1951

+1

Я не уверен, что полностью понимаю первый вопрос. Второй вопрос, конечно, сложный.Вы можете использовать * args и ** kwargs, чтобы сделать это так, чтобы все действия могли принимать одни и те же аргументы и просто не использовать некоторые из этих аргументов, или вам, возможно, потребуется сосать их и написать их все ... – mgilson

2

Вы можете создать класс со статическими методами и использовать getattr, чтобы получить правильный метод. Это похоже на то, что mgilson предполагает, но вы, по сути получить создание Dict бесплатно:

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) 

называется calcChurn
называется calcEngagement
называется calcPenetration
называется calcChurn
называется calcChurn
называется calcEngagement
Неизвестный статистика: undefined

+0

So то почему бы не использовать dict? Вы создаете неизвестные атрибуты. – Zizouz212

+0

Потому что таким образом вам нужно только реализовать метод и не беспокоиться о его добавлении в dict. Это всего лишь вопрос стиля и предпочтения. – Raniz

+0

Каждый способ - это разные шаги. Вы можете легко получить доступ к вещам. Независимо от того, +1, как это полезно. – Zizouz212

0

Возможно, имеет смысл инкапсулировать требуемые вызовы внутри объекта.

Если у вас есть смысл для объекта быть объектом, и особенно если многие клиенты называют один и тот же набор функций для получения показателей, то вы можете создать набор подклассов Client, которые вызывают предопределенный набор функций для получения метрики.

Это немного тяжелее, чем отображение 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() 

В результате вы получите от execute_metrics является dict отображение имени функции его результатов для этого клиента.