Python metaclass for randomized quiz questions
<p>For teaching a large math class, it's helpful to write questions with some randomization built in, so that not all students get the exact same questions.  My solution is to write a short Python script to generate a few questions -- identical except slight variations in the numbers.  The result might be to give the class versions A/B/C/D of a quiz, and automatically give me solution sets A/B/C/D for quick grading.</p>

<p>I'd like to formalize this a bit, to allow others with <em>basic</em> Python skills to create randomized quizzes.  Also, to organize my own work better.  My first idea was to make a module with a Question class, which would have methods for initialization, for rendering (producing LaTeX code to include in a quiz document), for assessing a given response/answer, and various other attributes.  </p>

<p>But alas, different instances of the Question class (different randomizable questions) would have different methods.  For example, one question might randomly generate two three-digit numbers x,y, and render the question as "Find the GCD of (x,y)".  At initialization, the question would also compute the answer, and reference it in the assessment method.  Another question might randomly generate a single three-digit number p, and render the question as "Is p prime?".  </p>

<p>A few solutions came to mind:</p>

<ol>
<li><p>Place code into string attributes, and call it via exec/eval.  This is bad.</p></li>
<li><p>Override various methods each time I write a new question. When I investigated this, I learned the term "monkey-patching" which also seems bad.</p></li>
<li><p>Use a Question metaclass!  Then, I guess that each randomizable question will end up being a class.</p></li>
</ol>

<p>I guess that solution 3 is the right one, so I'll have to learn metaclasses.  But it would be a bit sad too.  I would really like this to be usable by <em>beginner</em> programmers to write randomizable questions.  I don't want a beginner to have to declare a class for every question.</p>

<p>Alternative ideas?  My own skills are on the beginner side when it comes to object-oriented aspects of programming. </p>

<hr>

<h2>Edit:  perhaps some more information would be helpful.</h2>

<p>I want to be able to easily reference questions later, e.g. for producing an exam or solution sheet.  For example, I would want to pass a list of questions to a function, with a single command like "make_exam([q1,q2,q3], randomize=True)" where q1,q2,q3 are three questions.  The command would return LaTeX code for an exam with the question text for q1,q2,q3, suitably randomizing each one.  I could also turn on/off a solution sheet, etc..  For this, I think I need the question objects to come with various methods.  </p>

<p>There will be many questions and there will be randomization of some of the parameters within each question. I assume there is no randomization of the choice of questions, only their parameters (although that would be solved the same way).</p>

<p>So, you could have a class for each question:</p>

<pre><code>class QuestionTemplate1():
    def __init__(self):
        self.x = random.randint(100, 1000)
        self.y = random.randint(100, 1000)

    def get_question(self):
        return "Find the GCD of (%d, %d)" % (self.x, self.y)
</code></pre>

<p>To create a variation of a question, just make an instance. They will be random and they will be different:</p>

<pre><code>quesition1_a = QuestionTemplate1()
quesition1_b = QuestionTemplate1()
</code></pre>

<p>You'll have a list of all question templates <code>[QuestionTemplate1, QuestionTemplate2, QuestionTemplate3,...]</code> and create 4 questions for each.</p>

<p>The only thing left is repeatability. Can you cannot repeat the same random values later? Yes.</p>

<p>At the beginning of the script set the seed.</p>

<pre><code>random.seed(3543626)

questions_a = [Q() for Q in all_templates]
questions_b = [Q() for Q in all_templates]
questions_c = [Q() for Q in all_templates]
questions_d = [Q() for Q in all_templates]
</code></pre>

<p>If you run the same code again with the same seed, you will get the same random parameters. I would probably randomize the seed at the beginning and print it with the questions. Then later, I would be able to recreate the same questions if I wanted.</p>

<p>That is it. No <code>eval</code>, no <code>exec</code>, no monkeypatching, no metaclasses. Not even a super class.</p>

python metaclass