moin moin
moin moin

Reputation: 2453

python trick needed to convert dictionary of functions into dictionary of results

I am currently blank on how to do this in an effective way.I thought about using objects but I don't see how they could help in this case. Any ideas?

from random import choice
from copy import deepcopy


def main():

    def rand_int():
        return choice(['yes', 'no'])

    # any nesting, functions possible
    spec = {
        'answer': rand_int,
        'next': {'answer': rand_int},
        'the_answer': 42
    }

    #### looking for elegant (automatic) way to do this
    result = deepcopy(spec)
    result['answer'] = result['answer']()
    result['next']['answer'] = result['next']['answer']()
    #### until here

    # result2 = ...

    print 'result: %s' % result


if __name__ == '__main__':
    main()

please do not tell me to use xsd!

Upvotes: 1

Views: 596

Answers (1)

Gareth Latty
Gareth Latty

Reputation: 89057

You can do this with one line in a dictionary comprehension:

{key: function() for key, function in mydictionary.items()}

Of course, this will throw errors when a value isn't a function, so if that is a possibility, we can simply add a check with the callable() builtin:

{key: (function() if callable(function) else function) for key, function in mydictionary.items()}

We then need to deal with the fact that your answer needs to be recursive, this makes it a little more complex, but not too hard to fix:

def call_all_callables_in_dict(mapping):
    if hasattr(mapping, "items"):
        return {key: call_all_callables_in_dict(value) for key, value in mapping.items()}
    elif callable(mapping):
        return mapping()
    else:
        return mapping

Note that if you have objects with an items attribute or method you wish to store in a dict this function will be run on, this could cause problems. I would recommend changing the name of that attribute or method, or replacing the check with isinstance(dict).

I would also like to note that for misleading function names rand_int that returns a string of 'yes' or 'no' is probably about as bad as it gets. Generally you want True/False in those situations as well.

As noted in the comments, pre-Python 2.7, you may not have dictionary comprehensions. To get around this, dict() will take a generator of tuples, so you can replace a dict comprehension like so:

{x: y for x, y in something.items()}

With:

dict((x, y) for x, y in something.items())

So, in full:

from random import choice

def rand_int():
        return choice(['yes', 'no'])

spec = {
    'answer': rand_int,
    'next': {'answer': rand_int},
    'the_answer': 42
}

def call_all_callables_in_dict(mapping):
    if hasattr(mapping, "items"):
        return {key: call_all_callables_in_dict(value) for key, value in mapping.items()}
    elif callable(mapping):
        return mapping()
    else:
        return mapping

print(call_all_callables_in_dict(spec))

Gives us:

{'answer': 'no', 'the_answer': 42, 'next': {'answer': 'yes'}}

Upvotes: 8

Related Questions