can't see a way around global var - python

I am just learning to script python working my way thru MIT comp. science open course and I have a question. I have read that setting vars to global inside of functions is the root of, well at least some evil. Although I am sure there is an obvious answer to my question, I could not see a way around NOT setting a global var in my func 'rand_guess' because I need the values from the func in the main body's while loop to perform further processing. I hope then someone might describe a more acceptable (pythonic I think is the term) way I could do so. Here is my script:
# Instruct the user to pick an arbitrary number from 1 to 100 and proceed to
# guess it correctly within 10 attempts. After each guess, the user must tell
# whether their number is higher than, lower than, or equal to your guess.
def rand_guess(l, h):
# print "l: " + str(l)
# print "h: " + str(h)
# global rand_num
rand_num = random.randint(l, h)
print(str(cntr) + '_2: ' + str(rand_num)) # debug
if rand_num != user_num:
if cntr < attempts:
print '\nMy guess of ' + str(rand_num) + ' is wrong.'
# global hi_lo
hi_lo = raw_input('So should my next guess be [H]igher or [L]ower? ')
print(str(cntr) + '_2: ' + hi_lo) # debug
else:
print '\nMy guess of ' + str(rand_num) + \
' is wrong, and I am out of guesses.\n'
exit()
else:
print '\nWoW! I can hardly believe it! My guess is correct: ' + \
str(user_num) + '\n'
exit()
return rand_num
return hi_lo
import random
attempts = 5
rand_num = 1
hi_lo = ''
lo = 1
hi = 100
user_num = int(raw_input('\nEnter a number between 1 to 100: '))
cntr = 1
while cntr < (attempts+1):
# first guess will be a random num between 1 to 100 so first lo_num must be
print(str(cntr) + '_1: ' + hi_lo) # debug
print(str(cntr) + '_1: ' + str(rand_num)) # debug
# 1
if cntr == 1:
rand_guess(lo, hi)
else:
# if user indicated to guess Higher with next guess
print(str(cntr) + '_3: ' + hi_lo) # debug
if hi_lo == 'H':
# then last number guessed +1 should be the lowest number guessed
# for all future guesses
print(str(cntr) + '_3: ' + str(rand_num)) # debug
if rand_num > lo:
lo = rand_num + 1
# user indicated to guess Lower with next guess
else:
# then last number guessed -1 should be the highest number guessed
# for all future guesses
if rand_num < hi:
hi = rand_num - 1
# func call with new lo-hi guess brackets
rand_guess(lo, hi)
cntr += 1
In addition, I would appreciate reading any others constructive suggestions regarding obvious python formatting faux pas I might be making, as I realize format in python is of the utmost importance.
EDIT:
Ok so I replaced the global statements with return statements, like what I originally tried and still the values I expect to be returned seem to be causing me problems. I added in some additional print statements to try to help determine what value a given var is at different points in the code. Here is the output of this script, including python debug details:
Enter a number between 1 to 100: 50
1_1:
1_1: 1
1_2: 61
My guess of 61 is wrong.
So should my next guess be [H]igher or [L]ower? L
1_2: L
2_1:
2_1: 1
2_3:
Traceback (most recent call last):
File "ex_loops2.py", line 67, in <module>
rand_guess(lo, hi)
File "ex_loops2.py", line 10, in rand_guess
rand_num = random.randint(l, h)
File "/usr/lib/python2.7/random.py", line 241, in randint
return self.randrange(a, b+1)
File "/usr/lib/python2.7/random.py", line 217, in randrange
raise ValueError, "empty range for randrange() (%d,%d, %d)" % (istart, istop, width)
ValueError: empty range for randrange() (1,1, 0)
02:53 exercises $
The first empty value for print '2_1: ' should have been 'H' (in this instance) and the value '1' for the second `print' '2_1: 1' right before pythons throws its error I expected to be '2_1: 61' (again for this particular instance, the returned value of 'rand_num').
So again, if I simply global those vars the script works as expected. I am sure someone can point out my obvious oversight or omission or wrong assumption and use of python's return statement here, because I am stumped.
While I can see how Justin's modified example below is a much better revision of what I am trying to accomplish here, I really just to need to know what it is I am doing wrong trying to return these particular var values so they are available outside of the func in which they are assigned (since the script basically works otherwise). I think remaining in the context I am having problems will yield the greatest benefit regarding this basic aspect of python.

This isn't exactly what you are looking for, but it should give some pointers.
import sys
import random
def random_guess(low=1, high=100, attempts=10, hints=True):
"""Try to guess the random number.
Args:
low (int): Lowest number the value can be.
high (int): Highest number the value can be.
attempts (int): How many tries you get.
hints (bool): Change range boundaries.
"""
# Command line args come in as strings.
low = int(low)
high = int(high)
attempts = int(attempts)
hints = hints in [True, 1, "True", 'true', '1', 't', 'y', 'yes']
mynum = None
rand_num = random.randint(low, high)
for _ in range(attempts): # _ means the last command ... i isn't used. Skips the pylint warning
# Check input
if hints and mynum is not None:
if mynum < rand_num:
low = mynum + 1
print("Incorrect answer! Guess higher next time")
elif mynum > rand_num:
high = mynum - 1
print("Incorrect answer! Guess lower next time")
elif mynum is not None:
print("Incorrect answer!", end=" ")
# Ask the user for input (raw_input for python 2.x)
try:
mynum = int(input('Enter a number between '+str(low)+' to '+str(high)+': '))
if mynum == rand_num:
print("Congratulations, you win!")
return # skip the print you lose and exit the function
except ValueError:
print("That wasn't a number.")
# end for
print("Sorry you lose!")
# end main
# Use name to check if it is the main. This prevents the below code from executing on import.
# So another application and import the random_guess method and use it without actually running it.
if __name__ == "__main__":
commandline_args = sys.argv[1:] # First argument is usually the filename
random_guess(*commandline_args) # run the main method

Related

Mathematic questions in python

Looking on for some guidance on how to write a python code
that executes the following:
The program will ask for math problems to solve.
The program will asks for the number of problems.
And asks for how many attempts for each problem.
For example:
Enter amount of programs: 4
Enter amount of attempts: 5
what is: 4x3 =?
Your answer: 16
and so goes on to another attempt if wrong if correct moves onto another problem, just like before and exits when attempts or problems are finished.
I have this code but I want to it only do multiplication ONLY and would like to know how to integrate how to put additional code to limit how many time one can solve the question and how many questions it asks
import random
def display_separator():
print("-" * 24)
def get_user_input():
user_input = int(input("Enter your choice: "))
while user_input > 5 or user_input <= 0:
print("Invalid menu option.")
user_input = int(input("Please try again: "))
else:
return user_input
def get_user_solution(problem):
print("Enter your answer")
print(problem, end="")
result = int(input(" = "))
return result
def check_solution(user_solution, solution, count):
if user_solution == solution:
count = count + 1
print("Correct.")
return count
else:
print("Incorrect.")
return count
def menu_option(index, count):
number_one = random.randrange(1, 21)
number_two = random.randrange(1, 21)
problem = str(number_one) + " + " + str(number_two)
solution = number_one + number_two
user_solution = get_user_solution(problem)
count = check_solution(user_solution, solution, count)
def display_result(total, correct):
if total > 0:
result = correct / total
percentage = round((result * 100), 2)
if total == 0:
percentage = 0
print("You answered", total, "questions with", correct, "correct.")
print("Your score is ", percentage, "%. Thank you.", sep = "")
def main():
display_separator()
option = get_user_input()
total = 0
correct = 0
while option != 5:
total = total + 1
correct = menu_option(option, correct)
option = get_user_input()
print("Exit the quiz.")
display_separator()
display_result(total, correct)
main()
As far as making sure you're only allowing multiplication problems, the following function should work.
def valid_equation(user_input):
valid = True
for char in user_input:
if not(char.isnumeric() or char == "*"):
valid = False
return valid
Then after each user_input you can run this function and it will return True if the only things in the users string are numbers and the * sign and False otherwise. Then you just need to check the return value with a if statement that tells the user that their input is invalid if it returns False. You can add more "or" operations to the if statement if you want to allow other things. Like if you want to allow spaces (or char == " ").
As far as limiting the number of times a user can try to answer, and limiting the number of questions asked, you just need to store the values the user enters when you ask them these numbers. From there you can do nested while loops for the main game.
i = 0
user_failed = False
while ((i < number_of_questions) and (user_failed == False)):
j = 0
while ((j < number_of_attempts) and (user_correct == False)):
#Insert question asking code here
#In this case if the user is correct it would make user_correct = True.
j += 1
if j == number_of_attempts:
user_failed = True
i += 1
So in this situation, the outer while loop will iterate until all of the questions have been asked, or the user has failed the game. The inner loop will iterate until the user has used up all of their attempts for the question, or the user has passed the question. If the loop exits because the user used up all of their attempts, the for loop will trigger making the user lose and causing the outer loop to stop executing. If it does not it will add one to i, saying that another question has been asked, and continue.
These are just some ideas on how to solve the kinds of problems you're asking about. I'll leave the decision on how exactly to implement something like this into your code, or if you decide to change parts of your code to better facilitate systems like this up to you. Hope this helps and have a great one!

Why is a string printed although print isn't used?

This is an example program from the Python book that I am using to learn. What does the message += line signify? And how does it print that statement even though we've never used a print there?
This works as expected and returns $number is too high or $number is too low. But how does it do so even without print statement?
And what does message += str(guess) + .... do here, given that we've declared message as an empty string in the beginning (message = "")?
import random # We cover random numbers in the
rng = random.Random()
number = rng.randrange(1, 1000)
guesses = 0
message = ""
while True:
guess = int(input(message + "\nGuess my number between 1 and 1000:"))
guesses += 1
if guess > number:
message += str(guess) + " is too high.\n"
elif guess < number:
message += str(guess) + " is too low.\n"
else:
break
input("\n\nGreat, you got it in "+str(guesses)+" guesses!\n\n")
I tried using message like in the above program in one of my other scripts and it doesn't print statement like it does in the above program.
The call to input() includes message in its prompt.
Addition of strings concatenates them in Python. "ab" + "cd" produces "abcd". Similarly, message += "stuff" adds "stuff" to the end of the variable's earlier value. If it was the empty string before, "" + "stuff" produces simply "stuff".
The operator += is an increment assignment; a += b is short for a = a + b. (Some people also dislike the longer expression because it is mathematically implausible unless at least one of the values is zero; but = does not have its mathematical semantics anywhere else in Python, either.)

My while loop never reaches the conditional stament and keeps looping

The assignment was to make a guessing game where the parameter is the answer. At the end if the person gets it right, it prints a congratulatory statement and returns the number of tries it took, if they type in quit, it displays the answer and tries == -1. other than that, it keeps looping until they get the answer correct.
def guessNumber(num):
tries = 1
while tries > 0:
guess = input("What is your guess? ")
if guess == num:
print ("Correct! It took you" + str(tries)+ "tries. ")
return tries
elif guess == "quit":
tries == -1
print ("The correct answer was " + str(num) + ".")
return tries
else:
tries += 1
When i run it, no matter what i put in it just keeps asking me for my guess.
Since you called your variable num so I'm guessing it's a integer, you were checking equality between an integer and a string so it's never True. Try changing the num to str(num) when comparing, so:
def guessNumber(num):
tries = 1
while tries > 0:
guess = input("What is your guess? ")
if guess == str(num):
print ("Correct! It took you {0} tries. ".format(tries))
return tries
elif guess == "quit":
tries = -1
print ("The correct answer was {0}.".format(num))
return tries
else:
tries += 1
Is the code properly indented?
The body of the function you are defining is determined by the level of indentation.
In the example you pastes, as the line right after the def has less indentation, the body of the function is 'empty'.
Indenting code is important in python
Additionally, for assigning one value to a variable you have to use a single '=', so the:
tries == -1
should be
tries = -1
if you want to assign the -1 value to that variable.

Python 'Guess Your Age' Remember Last Integer

I am relatively new to programming with python (actually programming in general). I am making this 'Guess My Age' program that only has one problem:
import random
import time
import sys
print("\tAge Guesser!")
print("\t8 tries only!")
name = input("\nWhat's your name? ")
num = 80
min_num = 6
tries = 1
number = random.randint(min_num, num)
print("\nLet me guess... You are", number, "years old?")
guess = input("'Higher', 'Lower', or was it 'Correct'? ")
guess = guess.lower()
while guess != "correct":
if tries == 8:
print("\n I guess I couldn't guess your age....")
print("Closing...")
time.sleep(5)
sys.exit()
elif guess == "higher":
print("Let me think...")
min_num = number + 1 #### Here is my trouble - Don't know how to limit max number
time.sleep(3) # pause
elif guess == "lower":
print("Let me think...")
num = number - 1
time.sleep(3) # pause
number = random.randint(min_num, num) #<- Picks new random number
print("\nLet me guess... You are", number, "years old?")
guess = input("'Higher', 'Lower', or was it 'Correct'? ")
guess = guess.lower() #<- Lowercases
tries += 1 #<- Ups the tries by one
print("\nPfft. Knew it all along.")
time.sleep(10)
As you can see, I have 'num' as the max number for the random integer getting picked, but with:
elif guess == "higher":
print("Let me think...")
min_num = number + 1
it can go back up to however high it wants.
I want it to remember the last integer that 'num' was.
Say the program guessed 50 and I said 'Lower'. Then it said 30 and I said 'Higher'
I know I am probably sounding confusing, but please bear with me.
You need to define a maximum number as well as a minimum number. If they say their age is lower than a given age, you should set that age minus 1 as the maximum.
Of course, you also need to set an initial maximal age.
You might find it more useful to look into recursive functions for this kind of problem. If you define a function which takes min_age, max_age and tries_left as parameters, which comes up with a random number with between min_age and max_age and queries the user, you can then rerun the function (within itself) with a modified min_age, max_age and tries_left - 1. If tries_left is zero, concede defeat. This way you might get a better understanding of the logical flow.
I have left code out of this answer because, as you are a beginner, you will find it a useful exercise to implement yourself.
Cant you split out your guess into something like
max_num = 0
min_num = 0
elif guess =="lower":
max_num = number
if min_num!=0:
number = min_num+(max_num-min_num)/2
else:
number = max_num-1
elif guess =="higher":
min_num = number
if max_num!=0:
number=min_num+(max_num-min_num)/2
else:
number=min_num+1
Sorry it's not meant to be fully rigorous, and its a slight change on the logic you have there, but splitting out your variables so you have a higher and lower cap, that should help a lot?
Cheers
Please let me know if you need more elaboration, and I can try to write out a fully comprehensive version
It seems as though I was wrong in the fact that it did not remember the older integers. Before when running the program it would guess a number higher than the 'num' had specified. I don't know what I changed between then and now? But thank you for the help! #.#
This seems to work.
The only changes I really made:
-Variable names were confusing me, so I changed a couple.
-Note that if you try to mess with it (lower than 5, higher than 3... "Is it 4?" if you say it's higher or lower, you'll get an error).
The first time you set min and max numbers, you do it outside of the loop, so this script does "remember" the last guess and applies it to the new min, max inside of the loop. Each time it runs, the min will get higher or the max will get lower, based on the feedback from when the user checks the guess. If you had stuck the "min_num=6" and the "num=80" inside of the loop, the guesses would never get better.
import random
import time
import sys
print("\tAge Guesser!")
print("\t8 tries only!")
name = input("\nWhat's your name? ")
max_num = 10
min_num = 1
tries = 1
guess = random.randint(min_num, max_num)
print("\nLet me guess... You are", guess, "years old?")
check = raw_input("'Higher', 'Lower', or was it 'Correct'? ")
check = check.lower()
while check != "correct":
if tries == 8:
print("\n I guess I couldn't guess your age....")
print("Closing...")
time.sleep(5)
sys.exit()
elif check == "higher":
print("Let me think...")
min_num = guess + 1
time.sleep(3) # pause
elif check == "lower":
print("Let me think...")
max_num = guess - 1
time.sleep(3) # pause
guess = random.randint(min_num, max_num) # <- Picks new random number
print("\nLet me guess... You are", guess, "years old?")
check = input("'Higher', 'Lower', or was it 'Correct'? ")
check = check.lower() # <- Lowercases
tries += 1 # <- Ups the tries by one
print("\nPfft. Knew it all along.")
time.sleep(10)

PYTHON: Error Message in random.py? Used to work, then it didn't, then it did, now it doesn't again. What's happening?

I've been learning python for a few months now, and usually I've been able to overcome all the problems I face, but now I'm at a loss. I'm writing a program called 'Quizzer' that will be used to generate random questions based on lists of terms and answers that Python is given.
My main problem has with the the gen_question function I've been working on. I wanted Python to receive a term, and output four multiple choice answers: One the actual, and three randomly selected from the pool of all possible answers. I had to include several checks to make sure the selected random answers were not the real answer and were not the same as each other.
I finally got it to work today, and then a bit later I tested it. I got an error message (that I will display in a second). I undid everything back to where I was earlier and I still got the same error message. After a few hours I came back, and I got it again. Out of frustration, I retried, and it worked. Now it isn't working anymore. Please, anyone: What is going on?
Here is my code (I don't know what's necessary so I am including the entire thing):
#import random for generating
import random
#term and definition libraries
terms_ans ={'term1':'answer1','term2':'answer2','term3':'answer3','term4':'answer4','term5':'answer5','term6':'answer6','term7':'answer7','term8':'answer8','term9':'answer9','term10':'answer10','term11':'answer11','term12':'answer12','term13':'answer13','term14':'answer14','term15':'answer15','term16':'answer16','term17':'answer17','term18':'answer18','term19':'answer19','term20':'answer20'}
term_list = ['term1','term2','term3','term4','term5','term6','term7','term8','term9','term10','term11','term12','term13','term14','term15','term16','term17','term18','term19','term20']
answer_list = ['answer1','answer2','answer3','answer4','answer5','answer6','answer7','answer8','answer9','answer10','answer11','answer12','answer13','answer14','answer15','answer16','answer17','answer18','answer19','answer20']
#picks the test questions to ask
def gen_test(amount=len(term_list)):
found_starter = False
test_terms = []
while found_starter == False:
#pick a random starting point in the terms to see if it is suitable
start_point = random.randint(1, len(term_list))
if amount == len(term_list):
#if user inputs max amount of questions possible, just take the term list
test_terms = term_list
found_starter = True
elif len(term_list) - (start_point + amount) >= 0:
#if it is suitable, then append the terms to the test questions
for x in xrange(start_point,start_point+amount):
test_terms.append(term_list[x])
found_starter = True
else:
return test_terms
#scramble list
def list_scrambler(unscrambled_list):
test_terms=[]
countdown = len(unscrambled_list) + 1
for x in range(1, countdown):
transfer_var = random.randint(0,len(unscrambled_list)-1)
test_terms.append(unscrambled_list[transfer_var])
del unscrambled_list[transfer_var]
return test_terms
#ask user for amount of questions needed and get the list
test_terms = list_scrambler(gen_test(int(raw_input("How many questions on your test? (There are " + str(len(term_list)) + " questions in total.) "))))
def gen_question(picked_term, question_num=1, total_amount=len(test_terms)):
#print start of question
print
print "Question " + str(question_num) + " of " + str(total_amount) + ":"
print
print picked_term
print
#gather random multiple choice answers they must a) all be different and b) not be the answer
ans_1_acceptable = False
while ans_1_acceptable == False:
int_rand_ans_1 = random.randint(1, len(term_list)) - 1
if str(term_list[int_rand_ans_1]) != str(picked_term):
#Term accepted; send to output
ans_1_acceptable = True
ans_2_acceptable = False
while ans_2_acceptable == False:
int_rand_ans_2 = random.randint(1, len(term_list)) - 1
if int_rand_ans_2 != int_rand_ans_1 and str(term_list[int_rand_ans_2]) != str(picked_term):
ans_2_acceptable = True
ans_3_acceptable = False
while ans_3_acceptable == False:
int_rand_ans_3 = random.randint(1, len(term_list)) - 1
if int_rand_ans_3 != int_rand_ans_1 and int_rand_ans_3 != int_rand_ans_2 and str(term_list[int_rand_ans_3]) != str(picked_term):
ans_3_acceptable = True
#Decide if the correct answer is A, B, C, or D
correct_ans = random.randint(1,4)
#Print the options using the variables gathered above
if correct_ans != 1:
print "A) " + answer_list[int_rand_ans_1]
else:
print "A) " + terms_ans[picked_term]
if correct_ans != 2:
print "B) " + answer_list[int_rand_ans_2]
else:
print "B) " + terms_ans[picked_term]
if correct_ans != 3:
print "C) " + answer_list[int_rand_ans_3]
else:
print "C) " + terms_ans[picked_term]
if correct_ans == 1:
print "D) " + answer_list[int_rand_ans_1]
elif correct_ans == 2:
print "D) " + answer_list[int_rand_ans_2]
elif correct_ans == 3:
print "D) " + answer_list[int_rand_ans_3]
else:
print "D) " + terms_ans[picked_term]
print
Now, usually it outputs everything like you'd expect. I don't have a feature to automatically generate questions yet so I have to type in the line:
gen_question('term1')
or whatever term I'm using.
Here is the output I've been getting:
How many questions on your test? (There are 20 questions in total.) 20
>>> gen_question('term1')
Question 1 of 20:
term1
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
gen_question('term1')
File "C:\Users\Owner\Desktop\LEARNING PYTHON\scripts\in progress\Quizzer.py", line 69, in gen_question
int_rand_ans_1 = random.randint(1, len(term_list)) - 1
File "C:\Users\Owner\Desktop\LEARNING PYTHON\python 2.7.5\lib\random.py", line 241, in randint
return self.randrange(a, b+1)
File "C:\Users\Owner\Desktop\LEARNING PYTHON\python 2.7.5\lib\random.py", line 217, in randrange
raise ValueError, "empty range for randrange() (%d,%d, %d)" % (istart, istop, width)
ValueError: empty range for randrange() (1,1, 0)
>>> gen_question('term8')
This is what is getting you:
term_list = [...]
is defined at the start of your file, but later on you do the following when the amount entered is the max.
test_term = term_list
This does not create a copy of your array, this creates two variables which both reference the same array. So any further modifications to test_term are actually reflected against the list referenced by both variables.
And since you are defining test_terms at a global level in the script you NUKE it when you make this call
def list_scrambler(unscrambled_list):
test_terms=[]
countdown = len(unscrambled_list) + 1
for x in range(1, countdown):
transfer_var = random.randint(0,len(unscrambled_list)-1)
test_terms.append(unscrambled_list[transfer_var])
del unscrambled_list[transfer_var]
return test_terms
Also to add,
Hungarian notation is a big no-no and python is a strongly typed language anyways. If you are having a hard time keeping track of times don't rely on variable names. Instead get yourself an IDE or use names expressive of what they are doing.
if something == false:
should be rewritten as
if not something
This one is more for preference, but when printing out text that needs to have data floated in, you can save yourself some headache and write
"D) {0}".format(somelist[index])
This will stuff the variable into the {0} and provides you with some formatting context and prevents you from having to str() an object.
Also, globals in general are considered a bad thing, they're debatable. like globals in C sometimes they serve a clear purpose, but for the most part they hide bugs and make issues harder to track. Also sometimes your variable declarations will shadow globals, others (as you saw) will let you screw things up.
Well, it's pretty obvious that randint() is complaining because term_list is empty, right? Then
random.randint(1, len(term_list))
is
random.randint(1, 0)
and randint is stuck. So why is term_list empty? It's because this statement:
test_terms = list_scrambler(gen_test(int(raw_input("How many questions on your test? (There are " + str(len(term_list)) + " questions in total.) "))))
destroys term_list, and that's probably ;-) not intended.
It's hard to follow the code to track down why that happens. The basic problem is that gen_test can set
test_terms = term_list
and then end up returning term_list under the name test_terms. Then term_list is still intact at the start of list_scrambler, but empty by the time list_scrambler ends. The
del unscrambled_list[transfer_var]
deletes all the elements in term_list, one at a time.

Categories

Resources