4
\$\begingroup\$

Made this generator to practice using imports from other modules and better readability for coding. What could I have done better and what did I do wrong?

File called test_generator.py

from test_dictionary import math as Math, trivia as Trivia
import random

math = list(Math.keys())
trivia = list(Trivia.keys())

def getQuestions(question_type):
    question_list = []
    for i in range(random.randint(1,5)):
        random_index = random.randint(0, len(question_type) - 1)
        question_list.append(question_type[random_index])
            
    return question_list

def answerChecker(question_list, answer_list, question_type):

    correct_answer = 0
    for index in range(len(question_list)):
        if question_type[question_list[index]] == answer_list[index]:
            correct_answer += 1
    
    return correct_answer

def questionPrinter():
    for question in math_questions:
        print('\n' + question)
        math_answers.append(input('Answer? ').lower())

    for question in trivia_questions:
        print('\n' + question)
        trivia_answers.append(input('Answer? ').lower())


math_questions = getQuestions(math)
trivia_questions = getQuestions(trivia)
math_answers = []
trivia_answers = []

questionPrinter()

correct_answer = answerChecker(math_questions, math_answers, Math)
correct_answer += answerChecker(trivia_questions, trivia_answers, Trivia)
print(f'{correct_answer}/{len(math_questions) + len(trivia_questions)}')

File called test_dictionary.py

math = {
    'What is the square root of 121?': '11',
    'Who created the Pythogorean theorem?': 'pythagoras',
    'What is 4 cubed?': '64',
    '550 / 5? ': '110',
    'If 1=3, 2=3, 3=5. 4=4, 5=4, Then 6 = what? ': '3',
    '8.563 + 4.8292? ': '13.3922',
    'I am an odd number. Take away one letter and I become even. What number am I? ' : '7',
    'Sally is 54 years old and her mother is 80, how many years ago was Sally’s mother times her age? ': '41',
    'There is a three-digit number. The second digit is four times as big as the third digit, while the first digit is three less than the second digit. What is the number? ': '141',
    'How many feet are in a mile? ': '5280',
    'Solve  -15 + (-5x) = 0': '-3',
    'What is 1.92÷3': '0.64',
    'If 72 x 96 = 6927, 58 x 87 = 7885, then 79 x 86 = what? ': '6897',
    'Look at this series: 36, 34, 30, 28, 24, … What number should come next? ': '22',
    'Look at this series: 22, 21, 23, 22, 24, 23, … What number should come next? ': '25',
    'If 13 x 12 = 651 & 41 x 23 = 448, then, 24 x 22 = what? ': '924',
    'Look at this series: 53, 53, 40, 40, 27, 27, … What number should come next? ': '14'
}


trivia = {
    'What was the 43 state to join the USA? ': 'idaho',
    'What year was the very first model of the iPhone released? ': '2007',
    'Is Java a type of OS? ': 'no',
    'Which email service is owned by Microsoft? ': 'hotmail',
    'What was Twitter’s original name? ': 'twttr',
    'What is the symbol for potassium? ': 'k',
    'Which planet has the most gravity? ': 'jupiter',
    'Which animal can be seen on the Porsche logo? ': 'horse',
    'Which company owns Bugatti, Lamborghini. Audi, Porsche, and Ducati? ': 'volkswagen',
    'In what year was the Corvette introduced? ': '1953',
    'How many soccer players should each team have on the field at the start of each match? ': '11',
    'Which country invented tea? ': 'china',
    'Which country is responsible for giving us pizza and pasta? ': 'italy',
    'What is your body’s largest organ? ': 'skin',
    'What kind of cells are found in the brain? ': 'neurons',
    'Which continent is the largest? ': 'asia',
    'In which year did World War I begin?': '1914',

}

Unused file called test_grabber - I used this one to web scrape math questions, but at the end of the day I would still have to edit/change most of the questions, so I tpyed(ctrl+c and ctrl+v) the questions myself with minimal tweaking.

import bs4
import requests

request = requests.get('https://www.gkgigs.com/maths-general-knowledge-questions-and-answers/#:~:text=Maths%20GK%20Questions%20And%20Answers%201%201.%20Who,of%20the%20lever%20and%20pulley%3F%20...%20More%20items').text
website_html_content = bs4.BeautifulSoup(request,'lxml')

website_questions = website_html_content.select('.schema-faq-question')
website_answers = website_html_content.select('.schema-faq-answer')


def creating_question_list():
    all_questions = []
    for question in website_questions:
        all_questions.append(question.text)
    return all_questions

def creating_answer_list():
    all_answers = []
    for answer in website_answers:
        all_answers.append(answer.text[8::])
    return all_answers

all_questions = creating_question_list()
all_answers = creating_answer_list()
print(all_answers)

with open('Math_Questions', 'w+') as f:
    f.write('math = {\n')
    for index in range(len(all_questions)):
        f.write(f"\t'{all_questions[index]}' : '{all_answers[index]}',\n")
    f.write('}')
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

test_*.py is a bad name for the file. As per convention test_*.py files are for unit tests. A better word would be 'quiz'.

There is no need to write separate codes for math and trivia. You can modify your dictonary.py as shown below. This way you can easily add new subjects like say geography. It is better if you use yaml instead of .py files. Especially if you are web scraping.

questions = {
'math' : 
    {
        'What is the square root of 121?': '11',
        'Who created the Pythogorean theorem?': 'pythagoras',
        ...
    },
'trivia' : 
    {
        'What was the 43 state to join the USA? ': 'idaho',
        'What year was the very first model of the iPhone released? ': '2007',
        ...
    },
'geography' : 
    {
        'Which is the largest river in South America?': 'Amazon',
        'Which is the southermost continent?' : 'Antartica',
        ...
    }
}

Instead of using random.randint. You can use sample to get a list of random questions.

Here is the refactored code.

import test_dictionary
import random

def make_quiz(questions, question_distribution):
    return {
        section : random.sample(list(questions[section].items()), k = question_distribution[section])
        for section in question_distribution.keys()
    }

def take_quiz(quiz):
    answers = {}
    for section, questions in quiz.items():
        print(f'---- {section} section ----')
        print(f'Total Questions: {len(questions)}')
        answers[section] = []
        for i, question in enumerate(questions):
            print(f'{i}) {question[0]}')
            answers[section].append(input('Answer: '.lower()))

    return answers

def check_answers(quiz, answers):
    return {
        section : [
            question[1] == answer 
            for question, answer in zip(questions, answers[section])
        ]
        for section, questions in quiz.items()
    }

def display_result(result):
    
    print('---- Result ----')
    total_score = 0
    max_score = 0
    for section, section_result in result.items():
        section_score = section_result.count(True)
        total_score += section_score
        max_section_score = len(section_result)
        max_score += max_section_score
        print(f'{section} = {section_score} out of {max_section_score}')

    print(f'Total Score = {total_score} out of {max_score}')


def main():

    quiz = make_quiz(
        test_dictionary.questions,
        {
            'math' : 2,
            'trivia' : 1,
            'geography' : 1
        }
    )
    answers = take_quiz(quiz)
    print(answers)
    result = check_answers(quiz, answers)
    display_result(result)

    

if __name__ == '__main__':

    main()
```
\$\endgroup\$
5
  • \$\begingroup\$ Antartica is yet to be discovered. \$\endgroup\$
    – AcK
    Commented Mar 11, 2023 at 10:52
  • \$\begingroup\$ Thank you for the advice. Can you please explain what exactly return { section : random.sample(list(questions[section].items()), k = question_distribution[section]) for section in question_distribution.keys() this returns because I don't know what this returns. \$\endgroup\$
    – Beginner
    Commented Mar 11, 2023 at 16:37
  • \$\begingroup\$ And another question as to why yaml is better than .py files for the dictionaries because when I tried to use it it didn't recognize it as a module. \$\endgroup\$
    – Beginner
    Commented Mar 11, 2023 at 17:31
  • \$\begingroup\$ @Beginner That's a dictionary comprehension: see the Python tutorial). Within that, you have random.sample, to choose the correct number of items. (The list wrapper is not currently necessary, but may be needed in future versions of Python.) .keys() is not necessary, since iterating through a dictionary will already give you its keys. \$\endgroup\$
    – wizzwizz4
    Commented Mar 11, 2023 at 19:19
  • \$\begingroup\$ Also, I'd advise TOML or (in this case) JSON, INI or CSV, over YAML. YAML has a few issues, and provides a large arbitrary-code-execution attack surface. No more than using a Python file, if you're aware of the issues, but most people don't think of YAML as source code (which it is). \$\endgroup\$
    – wizzwizz4
    Commented Mar 11, 2023 at 19:29
1
\$\begingroup\$

Namespacing

You have test_generator and test_dictionary. It might be better to put these in one package, so they're test.generator and test.dictionary. (Also, test might not be the best name for it.)

PEP-8

Imports

Imports should be grouped in the following order:

  1. Standard library imports.
  2. Related third party imports.
  3. Local application/library specific imports.

You should put a blank line between each group of imports.

import random

from test_dictionary import math as Math, trivia as Trivia

Naming

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

Rather than getQuestions, consider get_questions.

Constants are usually defined on a module level and written in all capital letters with underscores separating words. Examples include MAX_OVERFLOW and TOTAL.

It might make sense to write MATH and TRIVIA.

Iteration

zip

correct_answer = 0
for index in range(len(question_list)):
    if question_type[question_list[index]] == answer_list[index]:
        correct_answer += 1

Whenever you see range(len(...)), you should be suspicious. Python's iteration is quite powerful; you can probably iterate directly. And yes, the standard library provides a function: zip.

correct_answer = 0
for question, answer in zip(question_list, answer_list):
    if question_type[question] == answer:
        correct_answer += 1

And since you're just summing a running total, you can use the sum function with a genexpr:

return sum((
    1
    for question, answer in zip(question_list, answer_list)
    if question_type[question] == answer
))

or

return sum(
    question_type[question] == answer
    for (question, answer) in zip(question_list, answer_list)
)

Dictionary

math = list(Math.keys())

Iterating over a dictionary will give you the keys. This bit, and the whole math / Math distinction, is redundant.

Nouns and verbs

answerChecker and questionPrinter are nouns, but they describe processes, not objects. Consider check_answers and print_questions.

creating_question_list: consider the imperative, create_question_list.

Globals

You should avoid modifying a global variable from inside a function: that can get quite messy. Instead of questionPrinter, consider something like:

def ask_questions(questions):
    answers = []
    for question in questions:
        print()
        print(question)
        answers.append(input('Answer? ').lower())
    return answers

math_answers = ask_questions(math_questions)
trivia_answers = ask_questions(trivia_questions)

Multiple answers

Some of your questions have multiple possible answers. 7 and seven, 141 and 582… It might be better to use:

  • a tuple or set of multiple answers, checked with answer in answers
  • a regular expression, checked with re.match
  • a predicate function (callable, returning a boolean), checked by calling it.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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