I have a fairly substantial Python project that I have developed as an educational endeavor. The version of the code that shall be given to students has exercises throughout, in the form of code that I have deleted and replaced with an exception. To be specific:

    def mk_eq_term(i):
        # raise ExerciseError("BitBlaster::bitblast_EQ_Core")
        return PL.And(val,PL.Iff(le.bits[i],re.bits[i]))

The last line is the real code; the second line is what shall be in the code given to students, once un-commented. I am not an expert on version control (in fact, at present, I do not use revision control), but my reading on the subject indicates that this use case is suboptimal. It seems like I will have to maintain two separate branches, and that any change I make against the "production" version will need to be reconciled against the "educational" version, even though I do not expect or intend to make changes to the codebase that will modify the exercise solutions.

What is the best solution for maintaining something like this?

  • Thinking about this, and it might spur someone else to an answer (if not, I'll see about answering it). One version with commit hooks to identify lines published with the comments #student and #teacher that then get transformed and published into other branches. That said, this seems RubeGoldberg-ish and there might be a better solution. – user40980 Jan 2 '15 at 4:00
  • You could just use the C preprocessor for this, with #ifdefs. The C preprocessor isn't going to care that it's a python program. – whatsisname Jan 2 '15 at 5:24
  • If you're not going to change anything, what is the problem? – JeffO Jan 2 '15 at 19:59

When you use git as version control you can.

  • use your full code as trunk.

  • branch of your students version.

  • continue dev on trunk.

  • merge trunk into branch.

  • do new adjustments for changed code in students branch.

All changes in the students branch will persist, git will only take new changes from trunk and add those to the branch, You will probably see conflicts when changing code on trunk that you have also altered on branch, but those should be easy to resolve. In most if not all cases you will want to take the trunk version, then do your fresh adjustments to branch.


Version control really is the wrong tool for this. (Of course, you should still be using version control for your code!)

You don't have separate but inter-related lines of development for your code. Instead, what you have is two completely different pieces of code.

However, those different pieces of code are related in such a way that one can be (semi-)automatically derived from the other. So, that's what you should do.

Use some kind of templating or preprocessing language or text manipulation tools (sed, awk, the C preprocessor, T4, …) to generate the student version from the master version.

This can be as simple as using the C preprocessor something like this:

def mk_eq_term(i):
#ifdef STUDENT
    raise ExerciseError("BitBlaster::bitblast_EQ_Core")
    return PL.And(val,PL.Iff(le.bits[i],re.bits[i]))

You could also use Python's ast module to manipulate the Abstract Syntax Tree for the code and something like the Meta library to write it back out again:

Say, you had written your function in the following way:

def mk_eq_term(i):

    return PL.And(val,PL.Iff(le.bits[i],re.bits[i]))

Then you could use the following script to transform all functions into their student versions like so:

from ast import *
class RewriteFunctions(NodeTransformer):
    def visit_FunctionDef(self, node):
        exception = get_docstring(node)
        if not exception: return node
        node.body = [Raise(Call(Name('ExerciseError', Load()), [Str(exception)], [], None, None), None, None)]
        return node

tree = parse(code) # assuming code contains the Python code to rewrite

At this point, you have an AST with all functions that have docstrings modified so that the only statement in their body raises an exception whose name is the content of the docstring.

Of course, you will still have to unparse the AST into Python source, there are various third-party libraries to do that.

  • Unfortunately, the Python example is incomplete, since I didn't want to install third-party libraries just to answer this question. Feel free to update the answer! – Jörg W Mittag Jan 2 '15 at 23:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.