HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

OO structure for holding and scoring exam data

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
scoringexamforstructureanddataholding

Problem

I'm writing some code to administer and grade multiple choice exams. I'd like this to be easily reusable, and I don't really want to have to do things like lay out quizzes and score them with a bunch of list and dict indexing for things like answers, prompts, etc.

I've tried to implement some classes to provide a consistent and simple interface for the rest of the code. (There's a long-term goal here of writing my own instructional management system.) The idea is to take an exam formatted as JSON with both standalone questions and questions that come in blocks, and sensibly represent them so that external code can just do easy stuff like pass a list of question-answer pairs to an exam object for grading.

This is a baseline for future functionality which will include programmatically adding/removing questions (and an interface for same), supplying iterable question-answer groupings to administer exams, providing a statistical summary of taker's scores by subject area, saving scores for later analysis etc.

My mind more naturally works in imperative and functional styles, not so much object-oriented, so I'd love some feedback on this implementation from that standpoint. Does this look like a sensible way of setting up the data structure? Is it appropriately pythonic? Am I violating any major OOP principles?

```
from collections import OrderedDict

class Question(object):
"""A single question with a unique correct answer.
Attributes:
block: string or None; string if this question belongs to block of
questions, where the string is unique identifier for that block.
qid: string, unique question identifier
prompt: string, question prompt
answers: ordered dict of {string, string} where first string is answer
ID as number/letter and second string is answer text.
correct: string, representing correct answerID
explanation: string containing explanation of correct answer.
subjects: li

Solution

OOP

Since you say you're new to OOP,
here are a few quick tips:

  • When designing classes, think in terms of Abstract Data Types (ADT): an ADT is data + the things you can do on that data, for some well-defined purpose



  • Encapsulation, information hiding: what information (data, operations) should the ADT hide? Internal details should be hidden from users.



So far you're doing fine, except for some naming issues.
For example looking at Question:

  • data: a question has an id, prompt, answers, etc, some of them with getters. So far so good.



  • operations: ask, ans, grade, dictRepr



The data of this ADT is fine.
The operations sound a bit odd.
It's not clear enough what they might do without looking at the implementation, and for example grade does something very different from what I would guess.
So there's at least a problem with naming.

On closer look, the methods return different representations of the question data as dictionary objects,
with some of the data intentionally hidden.
And I suppose ans is only used internally by grade.
And the code in ans is duplicated in dictRepr.
I suggest the following renames:

  • ask -> question_data_as_dict



  • dictRepr -> as_dict



  • ans -> eliminate, as_dict already does the same job



  • grade -> graded_data_as_dict



The idea is that if you look at these methods,
you understand better the operations on the ADT.

Coding style

Some general naming issues:

  • Variable names should be snake_case, not SHOUTCASE



  • Method names should be snake_case, not camelCase



See more tips in the coding style guide.

Naming

Some of the names are quite poor.
For example QBlock would be nicer to spell out as QuestionBlock.

In this piece of code:

for biter in qdict['blocks']:
    btemp = QBlock(biter['blockid'], biter['blockheader'])
    self.addBlock(btemp)
for qiter in qdict['questions']:
    qtemp = Question(qiter['block'], qiter['qid'], qiter['prompt'], qiter['answers'], qiter['correctans'],




qiter['explanation'], qiter['subjects'])
self.addQuestion(qtemp)

Some of the variables can be easily named better:

  • btemp -> block



  • qtemp -> question



The Pythonic way

Instead of this:

if not BLOCK:
    self.block = None
else:
    self.block = BLOCK


Probably this is good enough:

self.block = BLOCK


Instead of this:

if studAns == self.correct:
    tempans['gotRight'] = True
else:
    tempans['gotRight'] = False


You can use boolean expressions directly:

tempans['gotRight'] = studAns == self.correct

Code Snippets

for biter in qdict['blocks']:
    btemp = QBlock(biter['blockid'], biter['blockheader'])
    self.addBlock(btemp)
for qiter in qdict['questions']:
    qtemp = Question(qiter['block'], qiter['qid'], qiter['prompt'], qiter['answers'], qiter['correctans'],
if not BLOCK:
    self.block = None
else:
    self.block = BLOCK
self.block = BLOCK
if studAns == self.correct:
    tempans['gotRight'] = True
else:
    tempans['gotRight'] = False
tempans['gotRight'] = studAns == self.correct

Context

StackExchange Code Review Q#106490, answer score: 3

Revisions (0)

No revisions yet.