Means of generalizing similarly-structured return statement in many similarly-structured functions

Viewed 60

I have a little mathematics-practicing application in which, to add new problems, I just define a new function which returns some dictionaries like this:

def p4():
    vertices = random.randrange(1, 8)
    num_trees = random.randrange(2, 4)
    format_dict = {
        'vertices': vertices,
        'num_trees': num_trees
    }

    answer_dict = {
        'answer': vertices*num_trees, 
        'wrong_1': 0,
        'wrong_2': 1,
        'wrong_3': 'There do not exist any'
    }

    return {'question': format_dict, 'answers': answer_dict}

Just for some context, this is a Django app. where the text of each problem is stored in a model, and, when the user looks at the problem on the webpage, the text of the model is formatted according to format_dict and the multiple-choice answers are generated according to answer_dict.

Now, every problem, defined above, has the line return {'question': format_dict, 'answer': answer_dict}. Is there a way I can call a function like this, with these two dictionaries, and have the same functionality while omitting the return? I feel as if—if every function needs to have that line—then there's something I can do to generalize it (... A decorator, maybe? I've never used one, but now may be a good excuse).


The model method which processes the dictionaries looks like this:

def gen_QAPair(self):
        # The name/location of the problem's function is stored in 'module'
        module = importlib.import_module(self.QAModule) 
        # This calls the function (p4, for example) and gets the dictionaries
        qaPair = getattr(module, self.QAFunction)()
        # This takes the unformatted text and formats it according to 
        # the returned format dictionary
        formatted_text = self.unformatted_text.format(**qaPair['question'])

        return {'question': formatted_text, 'answers': qaPair['answers']}

The only thing I could think of was combining answer_dict and format_dict into a dictionary of dictionaries, and returning that—but it wouldn't really save much code, it'd just move defining the keys outside of the return statement.

2 Answers

I see two options.

First, you want to generalize that all problem functions return something of the form {'question': format_dict, 'answer': answer_dict}. You can represent this as a namedtuple, or if you have Python 3.7, a data class.

Named tuple example:

from collections import namedtuple
ProblemData = namedtuple('ProblemData', ['question', 'answer'])

def q_n():
    # ...
    return ProblemData(format_dict, answer_dict)

This solves the problem of the generalizing the structure somewhat, but you still have the return.

A much more general and more difficult approach would be to store each question in a configuration file. Of course, you would have to write some small language and parser to have the dynamic sort of data that you want, but I imagine if you scale up to thousands of questions it would be worth the effort.

Here's a way to make it a little less verbose:

def p4():
    vertices = random.randrange(1, 8)
    num_trees = random.randrange(2, 4)
    return dict(
        question = {
            'vertices': vertices,
            'num_trees': num_trees
        },
        answer = {
            'answer': vertices*num_trees, 
            'wrong_1': 0,
            'wrong_2': 1,
            'wrong_3': 'There do not exist any'
        }
    )

(I note you have a discrepancy between "answer" and "answers" in your examples.)

Another option is to return a tuple, and unpack the tuple in your gen_QAPair() function:

return ({
        'vertices': vertices,
        'num_trees': num_trees
    },
    {
        'answer': vertices*num_trees, 
        'wrong_1': 0,
        'wrong_2': 1,
        'wrong_3': 'There do not exist any'
    })

...then...

question, answer = getattr(module, self.QAFunction)()
Related