on perhaps unloading modules?

Hope Rouselle hrouselle at jevedi.com
Sun Aug 15 09:50:10 EDT 2021


(*) Introduction

By the way, I'm aware that what I'm doing here is totally unsafe and I
could get my system destroyed.  I'm not planning on using this --- thank
you for your concern.  I'm just interested in understanding more about
modules.

(*) The problem

I got myself into a mess with loading modules at runtime.  The reason
I'm loading modules at runtime is because I'm writing a script to grade
students' work.  (Each student test is a file.py.)

Here's the phenomenon.  Notice how student m0 (who really scored a zero)
first gets his grade right, but if I invoke it again, then it gets 50.0.

>>> grade_student(m0)
{'grade': 0.0, ...}
>>> grade_student(m0)
{'grade': 50.0, ...}

(*) Where's the problem?

The variable m0 is a module and modules in Python are effectively
singletons --- that is, they are meant to be loaded only once, no matter
how many times you ask the system to.  That's my understanding.

(*) How did I get into this mess?

When m0 is graded first, that is the first student's module ever loaded,
so everything turns out correct.  This module m0 didn't do any work ---
didn't write any procedures, so it gets grade = 0 ---, so when I check
the first procedure, it doesn't exist --- zero on question 1.  However,
question 2 depends on question 1.  So I use the test's key (which I
wrote, which is perfectly correct) and I augment the student's module
with the key's procedures that are prerequisites to question 2 and then
I test question 2.  How do I do that?

  I do m.question1 = key.question1

where ``key.question1'' is a correct procedure for getting all points of
question1.  (That's kind to the student: I'm allowing them to get a zero
on question 1 while perhaps getting question 2 right.)  However, once I
augment the student's code, I can' t find a way to properly restore it
to the original --- so on a second execution, m0 gets many more points.

That's not the whole problem.  For reasons I don't understand, new
modules I load --- that is, different students --- get mixed with these
modifications in m0 that I made at some point in my code.

Of course, you want to see the code.  I need to work on producing a
small example.  Perhaps I will even answer my own question when I do.

For now, let me just ignite your imagination.  Feel free to ignore all
of this and wait for a nice example of the problem.

(*) The code

How do I load a student's file?

--8<---------------cut here---------------start------------->8---
from importlib import *
def get_student_module(fname): 
  # Here fname := p1_hello.py. But we need to remove the extension.
  # Let's call it basename then.
  mod_name = basename(fname)
  try:
    student = import_module(mod_name)
  except Exception as e:
    return False, str(e)
  return True, student
--8<---------------cut here---------------end--------------->8---

Now let d be a path to a directory.  How do use I this procedure?

--8<---------------cut here---------------start------------->8---
  for f in get_all_tests(d):
    okay, student = get_student_module(f)
    report = grade_student(student)
    [...]
--8<---------------cut here---------------end--------------->8---

What does grade_student(student_module) do?  It passes student_module to
procedures to check every little thing the test requires.  Let's take a
look at question4().

Question 4 requires a previous procedure called procedure_x.  So I
overwrite the student's module with the key's procedure.

def question4(m):
  # We first provide students with all the prerequisites of the
  # question, so they don't necessarily get this one wrong by getting
  # prerequisites wrong.
  m.procedure_x = key.procedure_x
  
  # Now I make all my verifications about, say, m.procedure_y and I
  # compute an integer called losses, which I return.

  return losses

This strategy must be so unwise that it totally breaks my naïve dream of
automatic grading, showing how unfit I am for teaching students how to
program.  Such is life.

(*) If it were easy to unload modules...

I could just unload it and load it again.  That should restore the
student's module back to its original source code.


More information about the Python-list mailing list