patternpythonMinor
OO structure for holding and scoring exam data
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
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:
So far you're doing fine, except for some naming issues.
For example looking at
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
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
And the code in
I suggest the following renames:
The idea is that if you look at these methods,
you understand better the operations on the ADT.
Coding style
Some general naming issues:
See more tips in the coding style guide.
Naming
Some of the names are quite poor.
For example
In this piece of code:
qiter['explanation'], qiter['subjects'])
self.addQuestion(qtemp)
Some of the variables can be easily named better:
The Pythonic way
Instead of this:
Probably this is good enough:
Instead of this:
You can use boolean expressions directly:
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_dictalready 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, notSHOUTCASE
- Method names should be
snake_case, notcamelCase
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 = BLOCKProbably this is good enough:
self.block = BLOCKInstead of this:
if studAns == self.correct:
tempans['gotRight'] = True
else:
tempans['gotRight'] = FalseYou can use boolean expressions directly:
tempans['gotRight'] = studAns == self.correctCode 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 = BLOCKself.block = BLOCKif studAns == self.correct:
tempans['gotRight'] = True
else:
tempans['gotRight'] = Falsetempans['gotRight'] = studAns == self.correctContext
StackExchange Code Review Q#106490, answer score: 3
Revisions (0)
No revisions yet.