Search code examples
pythonpython-3.xdispatch

How to invoke Python methods using a dictionary


I'm trying to create a list (or dictionary) of methods that transform data. For example, I have data like below:

data = [
{'Result': 1, 'Reason1': False, 'Reason2': 1},
{'Result': 0, 'Reason1': False, 'Reason2':'haha'},
{'Result': 0, 'Reason1': True, 'Reason2': 'hehe'},
{'Result': 0, 'Reason1': True, 'Reason2': 0},
]


def rule_1(datum):
    modified_datum = datum
    if datum['Reason1']:
        modified_datum['Result'] = 1 # always set 'Result' to 1 whenever 'Reason1' is True
    else:
        modified_datum['Result'] = 1 # always set 'Result' to 0 whenever 'Reason1' is False
    return modified_datum


def rule_2(datum):
    modified_datum = datum
    if type(datum['Reason2']) is str:
        modified_datum['Result'] = 1 # always set 'Result' to 1 whenever 'Reason2' is of type 'str'
    elif type(datum['Reason2']) is int:
        modified_datum['Result'] = 2 # always set 'Result' to 2 whenever 'Reason2' is of type 'int'
    else:
        modified_datum['Result'] = 0
    return modified_datum


# There can be 'rule_3', 'rule_4' and so on... Also, these rules may have different method signatures (that is, they may take in more than one input parameter)
rule_book = [rule_2, rule_1] # I want to apply rule_2 first and then rule_1

processed_data = []
for datum in data:
    for rule in rule_book:
        # Like someone mentioned here, the line below works, but what if I want to have different number of input parameters for rule_3, rule_4 etc.?
        # processed_data.append(rule(datum))

I think this answer on Stack Overflow comes quite close to what I'm trying to do, but I'd like to learn from people who are experienced with Python on how to best handle it. I tagged this post with 'dispatch', which I think is the term it is called for what I'm trying to achieve(?) Thank you in advanced for your help and suggestions!


Solution

  • As commented, you're pretty close. All you need to do is call rule while you iterate through.

    In regards to handling varying length of params, you might opt to make use of *args and **kwargs in your rules. Here's a quick example:

    def rule1(*args, **kwargs):
        # Handling of non-keyword params passed in, if any
        if args:
            for arg in args:
                print(f'{arg} is type {type(arg)}')
        # if kwargs is not necessary if you don't intend to handle keyword params
    
    def rule2(*args, **kwargs):
        # if args is not necessary if you don't intend to handle non-keyword params
    
        # handling of keyword params passed in, if any
        if kwargs:
            for k, v in kwargs.items():
                print(f'Keyword arg {k} has value {v}')
    
    rule_book = [rule2, rule1]
    for rule in rule_book:
        # iterate through the rule_book with the same amount of args and kwargs
        rule('I am a string', 123, ('This', 'is', 'Tuple'), my_list=[0, 1, 2], my_dict={'A': 0, 'B': 1})
    

    Result:

    Keyword arg my_list has value [0, 1, 2]
    Keyword arg my_dict has value {'A': 0, 'B': 1}
    I am a string is type <class 'str'>
    123 is type <class 'int'>
    ('This', 'is', 'Tuple') is type <class 'tuple'>
    

    The key take away is keep the params consistent between your rules, once everything is passed in, just get the relevant object and make use of it:

    def rule3(*args, **kwargs):
        if args:
            for arg in args:
                if isinstance(arg, tuple):
                    # if there's a tuple presented, reverse each of the inner items
                    print([a[::-1] for a in arg])
    
     # ['sihT', 'si', 'elpuT']
    

    With the way you have structured your code, I'm confident you should be able to understand and apply this to your own.