You made a wise choice wanting to tango with the snake. Just as a mean teacher I will pick apart your code in excruciating detail. I really like your code for a beginner. It is clearly structured, it works and your naming is good.
With that being said, you do make some classical beginner mistakes
Just as learning a new language it is important to learn its grammar. Your speech can be technically correct and understandable, but one can tell it is not your mother tongue. The same can be said for your code. It looks like you have experience from a different language, and I would recommend getting familiar with Python's grammar.
PEP 8 recommends the following naming-conventions:
CAPITALIZED_WITH_UNDERSCORES
for constants
UpperCamelCase
for class names
lowercase_separated_by_underscores
for other names
So your functions should not follow cammelcase.
A concept you might find amusing is magic numbers. This is known as a code smell; something which should be avoided when programming. Programming is about making life easier, and to automate the boring stuff. Hardcoding numbers goes against this, and therefore should be avoided. The solution is to extract these numbers into constants following the PEP8 naming-conventions.
QUESTIONS = 42
def quiz():
print(f'Welcome! This is a {QUESTIONS} question quiz!\n')
score = 0
for i in range(QUESTIONS):
Did you notice the f
in the last code snippet? f-strings is the new way of formating strings in Python, and you should usually use them. For instance
print('What is {} {} {}?'.format(num1, op, num2))
becomes
print(f'What is {num1} {op} {num2}?')
Which is easier to read.
While I have nothing againt using libraries or the operator
module, I would suggest sticking to the basics when starting out. This part is heavily opinionated, but I would suggest not using code you do not understand. If you come back to the code half a year later, will you still remember the intricacies of op
?
You already know how to use if-else
so you could have done
operator_choice = random.randint(0, 1)
if operator_choice == 0:
answer = num1 - num2
elif operator_choice == 1:
answer = num1 + num2
handling user input
Never assume that your users input things exactly in the format you want.
def askQuestion():
answer = randomCalc()
guess = float(input())
return guess == answer
Here you need to do some checks to make sure the guess
is sanitized. What happens if the user inputs 2*3
? or exit
? A vague idea is something like
def get_valid_user_input():
try:
return float(input())
except ValueError:
print('Ooops, you need to input a number!')
Put the parts of your code that are the ones calling for execution behind a if __name__ == "__main__":
. This way you can import this python module from other places if you ever want to, and the guard prevents the main code from accidentally running on every import.
negative numbers
I gave you two suggestions to enforce positive numbers:
num1 = random.randint(0,20)
num2 = random.randint(0,20)
num1, num2 = max(num1, num2), min(num1, num2)
or
num1 = random.randint(0,20)
num2 = random.randint(0,num1)
I would recommend trying out these two solutions and try to figure out the difference. As a mathematician there is a difference in the type of numbers you get. Note that the first one also could have been implemented as
num1 = random.randint(0, MAX_NUM1)
num2 = random.randint(0, MAX_NUM2)
if num1 < num2:
num1, num2 = num2, num1
If you are not that comfortable with min
and max
.
Round two
I looked over your code again and found a few more things which I found odd. Remember how we talked about grammar at the start? It is somewhat peculiar when you say "this code does X" when it really does "X and Y" or even worse just "Y".
def askQuestion():
answer = randomCalc()
guess = float(input())
return guess == answer
This is not code that's just asking a question. It (1) takes in an answer from randomCalc
, (2) takes in a guess from the user, and (3) checks whether the guess from the user matches the answer. You are not really interested in asking a question, that would be more akin to
def askQuestion(question):
print(question)
What you are interested in is getting feedback from the user. So something akin to get_guess_from_player
is probably a better name.
Similarly randomCalc
is not just an calculator! It also asks the question!
def randomCalc():
.
.
.
print('What is {} {} {}?'.format(num1, op, num2))
Lastly you are doing something like
for i in range(42):
Which from a "grammar" (Pythonic way) should be
for _ in range(42):
since you never use the _
. However, since you never use
the for
loop, we can instead use something smarter.
suggestions
A clearer structure would be something like this: we define a function that only generates the question. We extract the magic numbers into constants at the start of the code
OPERATORS = ["+", "-"]
MAX_NUMBER = 20
QUESTIONS = 42
def generate_question():
operator = random.choice(OPERATORS)
num1 = random.randint(0, MAX_NUMBER)
num2 = random.randint(0, MAX_NUMBER)
if num2 > num1:
num1, num2 = num2, num1
return f"{num1} {operator} {num2}"
We create a function that only gets a valid guess from the user.
def get_guess():
try:
return float(input("> "))
except ValueError:
print("Ooops, you need to write in a number!")
Some fancy f-strings and a bit of cleanup the main function looks something like this.
def quiz():
print(f"Welcome! This is a {QUESTIONS} question quiz!\n")
score = 0
for _ in range(QUESTIONS):
print(question)
guess = get_guess()
if guess == eval(question):
Be wary; dragons lie ahead
A trap many new programmers falls into is premature optimization. However, I can not avoid noticing that the logic in the code could be improved. If you are asking multiple questions; why not generate all the numbers and operators at the start? It would lead to a cleaner and faster code. Notice how our main loop now reads
for question in generated_questions():
Much more Pythonic! I also included Toby's suggestion even if it is 100% overkill when doing basic arithmetic.
import numpy as np
QUESTIONS = 42
BAN_NEGATIVE_NUMBERS = True
OPERATORS = ["-", "+"]
MAX_NUMBER, DECIMALS = 20, 2
MAX_ERROR = 10 ** (1 - DECIMALS)
def generate_questions():
operators = np.random.choice(a=OPERATORS, size=(QUESTIONS, 1))
numbers = np.random.choice(a=MAX_NUMBER, size=(QUESTIONS, 2))
for operator, (num1, num2) in zip(operators, numbers):
if num1 < num2 and operator == "-" and BAN_NEGATIVE_NUMBERS:
num1, num2 = num2, num1
yield f"{num1} {operator[0]} {num2}"
def get_guess():
try:
return float(input("> "))
except ValueError:
print("Ooops, you need to write in a number!")
return float("Inf")
def quiz(score=0):
print(f"Welcome! This is a {QUESTIONS} question quiz!\n")
for question in generate_questions():
print(question)
guessed_right = abs(get_guess() - eval(question)) <= MAX_ERROR
score += 1 if guessed_right else 0
print("Correct!" if guessed_right else "Incorrect", "\nScore", score, "\n")
print(f"Your score was {score}/{QUESTIONS} = {round(100*score/QUESTIONS, 2)}")
if __name__ == "__main__":
quiz()
==
is risky in any language, due to precision issues. \$\endgroup\$