I'm new to Python and am really struggling to create a function for my quiz: I have created a while loop to offer different levels to the quiz player before they receive any questions. The while loop works on its own, but when i try to turn it into a function, the level variable stops working (returning only empty brackets outside of the code).
This is the while loop:
request_count = 0
level = ()
global_string_list = ()
while request_count < 1:
print user_level_options
level_request = raw_input("> ")
if level_request == "1":
level = string_1
global_string_list = string_1_list
if level_request == "2":
level = string_2
global_string_list = string_2_list
if level_request == "3":
level = string_3
global_string_list = string_3_list
if level_request == False:
print "Please try again."
request_count = request_count + 1
Could anybody please give me pointers on how to turn this into a function please?
Thank you!!
The level variable returns empty brackets because the level within the function is a local variable; completely different from the level you declared outside the function, which is outside the function's scope. This outside variable was hidden behind the local level variable, so all your assignments within the function were made to the local level and did not affect the level on the outside at all.
You have to either declare level as a global variable explicitly (generally considered bad practice) or you return your local level from the function. So perform all the changes you want in your function and then use the return statement like this:
def user_select(): # your function
#......your code goes here
return level
level = user_select() # the level variable within your function is now in this variable
I try to give you a jump start. But I won't give you the final solution.
In general, to make a function meaningful, you have to know why you need it.
I give you some example of the benefit using function.
Removal of repetitive code. (DRY, don't repeat yourself)
Identify a Pattern, and extract into a separate function. In your example, the repeating behavior can be seen near the condition inside the loop. (right after the raw_input function).
That is: if level_request == <something> then do <some-work>.
Thus, one improvement could be:
def updateLevel(level_request):
# Think about how you can determine the level without if-else
level = <?level?>
global_string_list = <?global_level|level?>
such that you only need to call:
while request_count < 1:
print user_level_options
level_request = raw_input("> ")
updateLevel(level_request)
if level_request == False:
print "Please try again."
request_count = request_count + 1
Abstraction
This is a very important topic in Computer Science and General Programming. You have to understand more to be able to appreciate the importance of abstraction.
E.g.
def askLevel():
print(user_level_options)
return raw_input("> ")
def displayError():
print("Please try again")
def incrementRequestCounter():
request_count += 1
such that you just need to call:
while request_count < 1:
level_request = askLevel()
updateLevel(level_request)
if level_request == False:
displayError()
incrementRequestCounter()
Note: in strict functional programming, function is a mapping of (input)->(output). And the function does not modify any global state (immutability). And all function must return a value. But in practice, we normally use a procedure instead. (procedure is a sequence of instruction). And def in python is both procedure and function.
Related
I'm writing a program that is basically a study guide/ practice test for the current section of my A&P class (it keeps me more engaged than just rereading notes over and over). The test works without any problems, but I have an issue where some of my questions use an "enterbox" input, I can have the question loop if the answer is incorrect, but I can't get it to break without a correct answer.
I figured out a way to make it work by putting the entire function back into the initial "else" tree, so that right or wrong you advance to the next question but it looks incredibly ugly and I can't believe there isn't a better way.
So my "solution" looks like such:
def question82():
x = "which type of metabolism provides the maximum amount of ATP needed for contraction?"
ques82 = enterbox(msg = x, title = version)
#version is a variable defined earlier
if ques82.lower() in ["aerobic"]:
add() #a function that is explained in the example further below
question83()
else:
loss() #again its a housecleaning function shown below
ques82b = enterbox(msg = x, title = version)
if ques82b.lower() in ["aerobic"]:
add()
question83()
else:
loss()
question83()
Okay so it worked, but using a nested if tree for each "enterbox" question looks kinda sloppy. I'm self taught so it may be the only solution but if there is something better I would love to learn about it.
So here is a complete section from my program:
from easygui import *
import sys
version = 'A&P EXAM 3 REVIEW'
points = 0
def add():
global points
msgbox("Correct", title = version)
points = points + 1
def loss():
global points
msgbox("Try Again", title = version)
points = points - 1
def question81():
x = "What chemical is stored by muscle as a source of readily available energy for muscle contractions"
ques81 = enterbox(msg = x, title = version)
if ques81.lower() in ["creatine"]:
add()
question82()
else:
loss()
question81()
It works as is so any errors from what's provided are probably my fault from copy and pasting.
Also I'm running it in python 2.7rc1 if that helps.
Thanks for any help in advance.
I don't know if there is a way to combine "enterbox" that has a button for "skip" as that would also be a solution.
Consider the following approach:
We define a list of question and answer pairs. We do this in one place so it's easy to maintain and we don't have to search all over the file to make changes or re-use this code for a different questionset.
We create an ask_question function that we can call for all of our questions. This way, if we want to make a change about how we implement our question logic, we only have to make it in one spot (and not in each of the questionXX functions).
We compare user input to our answer using == and not in (in will do something else, not what you expect).
We create an object to keep track of our answer results. Here, it's an instance of ResultsStore, but it can be anything really, let's just try to get away from global variables.
Use a loop when prompting for answers. The loop will repeat if the answer given was incorrect (and if retry_on_fail is False).
Allow for the user to enter some "skip" keyword to skip the question.
Display the results once the "test" is complete. Here, we do that by defining and calling the store.display_results() method.
So, what about:
from easygui import enterbox
question_answer_pairs = [
("1 + 1 = ?", "2"),
("2 * 3 = ?", "6"),
("which type of metabolism provides the maximum amount of ATP needed for contraction?", "aerobic")
]
VERSION = 'A&P EXAM 3 REVIEW'
class ResultStore:
def __init__(self):
self.num_correct = 0
self.num_skipped = 0
self.num_wrong = 0
def show_results(self):
print("Results:")
print(" Correct:", self.num_correct)
print(" Skipped:", self.num_skipped)
print(" Wrong: ", self.num_wrong)
def ask_question(q, a, rs, retry_on_fail=True):
while True:
resp = enterbox(msg=q, title=VERSION)
# Force resp to be a string if nothing is entered (so .lower() doesn't throw)
if resp is None: resp = ''
if resp.lower() == a.lower():
rs.num_correct += 1
return True
if resp.lower() == "skip":
rs.num_skipped += 1
return None
# If we get here, we haven't returned (so the answer was neither correct nor
# "skip"). Increment num_wrong and check whether we should repeat.
rs.num_wrong += 1
if retry_on_fail is False:
return False
# Create a ResultsStore object to keep track of how we did
store = ResultStore()
# Ask questions
for (q,a) in question_answer_pairs:
ask_question(q, a, store)
# Display results (calling the .show_results() method on the ResultsStore object)
store.show_results()
Now, the return value currently doesn't do anything, but it could!
RES_MAP = {
True: "Correct!",
None: "(skipped)",
False: "Incorrect" # Will only be shown if retry_on_fail is False
}
for (q,a) in question_answer_pairs:
res = ask_question(q, a, store)
print(RES_MAP[res])
Quick and dirty solution could be using the default value "skip" for the answer:
def question81():
x = "What chemical is stored by muscle as a source of readily available energy for muscle contractions"
ques81 = enterbox(msg = x, title = version, default = "skip")
if ques81.lower() == 'creatine':
add()
question82()
elif ques81 == 'skip':
# Do something
else:
loss()
question81()
But you should really study the answer given by jedwards. There's a lot to learn about
program design. He's not giving you the fish, he's teaching you to fish.
Thanks firstly for bearing with me as a relative newcomer to the world of Python. I'm working on a simple set of code and have been racking my brain to understand where I am going wrong. I suspect it is a relatively simple thing to correct but all searches so far have been fruitless. If this has been covered before then please be gentle, I have looked for a couple of days!
I'm working on the following and after catching and correcting a number of issues I suspect that I'm on the last hurdle:-
def main():
our_list = []
ne = int(input('How many numbers do you wish to enter? '))
for i in range(0, (ne)): # set up loop to run user specified number of time
number=int(input('Choose a number:- '))
our_list.append(number) # append to our_list
print ('The list of numbers you have entered is ')
print (our_list)
main()
while True:
op = input ('For the mean type <1>, for the median type <2>, for the mode type <3>, to enter a new set of numbers type <4> or 5 to exit')
import statistics
if op == "1":
mn = statistics.mean(our_list)
print ("The mean of the values you have entered is:- ",mn)
if op == "2":
me = statistics.median(our_list)
print ("The median of the values you have entered is:- ",me)
if op == "3":
mo = statistics.mode(our_list)
print ("The mode of the values you have entered is:- ",mo)
if op == "5":
main()
else:
print("Goodbye")
break`
For some reason the appended (our_list) is not being recognised within the while true loop rendering the statistics calculation void. Any steer would be really appreciated as to where I am missing the obvious, thanks in advance.
Cheers
Bryan
I'm not sure exactly what you mean by "not being recognized", but our_list is a local variable inside main, so it can't be used anywhere but inside main.
So, if you try to use it elsewhere, you should get a NameError.
If your code actually has a global variable with the same name as the local variable that we aren't seeing here, things can be more confusing—you won't get a NameError, you'll get the value of the global variable, which isn't what you want.
The best solution here is to return the value from the function, and then have the caller use the returned value. For example:
def main():
our_list = []
ne = int(input('How many numbers do you wish to enter? '))
for i in range(0, (ne)): # set up loop to run user specified number of time
number=int(input('Choose a number:- '))
our_list.append(number) # append to our_list
print ('The list of numbers you have entered is ')
print (our_list)
return our_list
the_list = main()
while True:
op = input ('For the mean type <1>, for the median type <2>, for the mode type <3>, to enter a new set of numbers type <4> or 5 to exit')
import statistics
if op == "1":
mn = statistics.mean(the_list)
print ("The mean of the values you have entered is:- ",mn)
if op == "2":
me = statistics.median(the_list)
print ("The median of the values you have entered is:- ",me)
if op == "3":
mo = statistics.mode(the_list)
print ("The mode of the values you have entered is:- ",mo)
if op == "5":
the_list = main()
else:
print("Goodbye")
break
There are other options—you could pass in an empty list for main to fill, or use a global variable (or, better, a more restricted equivalent like an attribute on a class instance or a closure variable), or refactor your code so everyone who needs to access our_list is inside the same function… but I think this is the cleanest way to do what you're trying to do here.
By the way, this isn't quite the last hurdle—but you're very close:
After any mean, median, or mode, it's going to hit the "Goodbye" and exit instead of going back through the loop. Do you know about elif?
You mixed up '5' and '4' in the menu.
If the user enters 2 and 3 and asks for the mode, your code will dump a ValueError traceback to the screen; probably not what you want. Do you know try/except?
That's all I noticed, and they're all pretty simple things to add, so congrats in advance.
The issue is that our_list was defined in the main() function, and is not visible outside of the main() function scope.
Since you're doing everything in one chunk, you could remove line 1 and 6, taking the code from your main() function and putting it on the same indentation level as the code which follows.
This seems to be because you defined our_list within the main() function. You should probably define it as a global variable by creating it outside the main() function.
You could also put the while loop inside a function and pass in our_list as a parameter to the list.
I have the following program and the variable(dictionary) in question is player_info that stores player information (name and goals). In order to solve the error that results currently, I simply need to make player_info a global variable, but I was wondering if stackoverflow experts could suggest or discuss the possibility of alternate ways of solving this problem WITHOUT the use of global variables.
Code
#FOOTBALL COACH app
#The program allows a user to enter a number of players (their names and goals scored) and then search for a player, returning their average goals for the three matches
import sys
def main():
mainmenu()
def mainmenu():
print("=====WELCOME to the MAIN MENU=============")
print("""
1..........Add New Players & Goals
2..........Search by Players
3..........Quit
=========================================
""")
choice=int(input("Enter choice:"))
if choice==1:
addplayers()
elif choice==2:
searchplayer(player_info)
elif choice==3:
sys.exit()
else:
print("You must make a valid choice - 1, 2 or 3")
def addplayers():
player_info= {} #create a dictionary that stores the player name: player goals
num_players = int(input("Please enter number of players you wish to enter:"))
print ("You are entering %s players" %num_players)
player_data = ['Match 1 goals : ', 'Match 2 goals : ', 'Match 3 goals : ']
for i in range(0,num_players):
player_name = input("Enter Player Name :")
player_info[player_name] = {}
for entry in player_data:
player_info[player_name][entry] = int(input(entry)) #storing the marks entered as integers to perform arithmetic operations later on.
mainmenu()
def searchplayer():
print("===============SEARCH by player: Calculate average goals==================")
name = input("Player name : ")
if name in player_info.keys():
#print student_info
print ("Average player goals : ", str(sum(player_info[name].values())/3.0))
else:
print("Please enter a valid player name:")
main()
As mentioned, I am aware that re-writing this in the addplayer() sub would fix the problem:
global player_info
player_info = {} #create a dictionary that stores the player name: player goals
...I am looking for ways to solve the problem WITHOUT the use of global variables.
Update:
One answer below using return player_info is what I would like to go with, but it doesn't quite work yet. Also, I need to return to the main menu each time a player is added, not quite sure how to do this, without a mainmenu call each time. Any suggestions? https://repl.it/JRl5/1
You can use return inside your function to avoid using global variables. A simple example is shown below:
def addplayers():
player_info= {}
name = input("Enter Name: ")
test = int(input("Enter a number: "))
player_info[name] = test
return player_info
player_info = addplayers()
If you then wanted to use this in another function you would just pass in the dictionary as an argument to that function:
def searchplayers(player_info):
print (player_info)
Note: An interesting answer on "Why are global variables evil?"
Edit:
Your addplayers() was calling mainmenu() which itself was being calling within mainmenu(). This is a recursive function and it might be best to avoid these unless there's a good reason for having it. I would put the contents of mainmenu inside a while loop until some condition is met. The complete code is shown below (I have removed the main function as it wasn't really doing anything):
def mainmenu():
stop = False
while stop == False:
print("=====WELCOME to the MAIN MENU=============")
print("""
1..........Add New Players & Goals
2..........Search by Players
3..........Quit
=========================================
""")
choice=int(input("Enter choice:"))
if choice==1:
player_info = addplayers()
elif choice==2:
searchplayer(player_info)
elif choice==3:
print ("Exit the main menu")
stop = True
else:
print("You must make a valid choice - 1, 2 or 3")
def addplayers():
player_info= {} #create a dictionary that stores the player name: player goals
num_players = int(input("Please enter number of players you wish to enter:"))
print ("You are entering %s players" %num_players)
player_data = ['Match 1 goals : ', 'Match 2 goals : ', 'Match 3 goals : ']
for i in range(0,num_players):
player_name = input("Enter Player Name :")
player_info[player_name] = {}
for entry in player_data:
player_info[player_name][entry] = int(input(entry)) #storing the marks entered as integers to perform arithmetic operations later on.
return player_info
def searchplayer(player_info):
print("===============SEARCH by player: Calculate average goals==================")
name = input("Player name : ")
if name in player_info.keys():
#print student_info
print ("Average player goals : ", str(sum(player_info[name].values())/3.0))
else:
print("Please enter a valid player name:")
mainmenu()
Store everything related to the game in a data structure, for example a dictionary, and pass it along in all functions where it can be updated as needed. Write a function "newgame" that creates this structure and initialises it.
In a way, this is object-oriented programming without using Python's syntax for classes and objects. Probably, you will learn these later in your class / tutorial.
Firstly, it is always possible to avoid using global variables. Secondly, global variables are possibly a misnomer in Python; global stores the variable in the local globals, which is typically the local module. That avoids a large part of the problem languages like C have with globals, in that they collide; Python has a namespace per module. For a simple script, where there is only one context, that might be fine.
Another namespace you might use is that of a particular object, using a class. This might look like:
class Game:
def mainmenu(self,...):
self.addplayers()
def addplayers(self):
self.player_info = {}
With that sort of code, whoever instantiates Game can make multiple instances, each passed as self when used. This is in large part syntactic sugar for a similar form of mutable state passing:
def mainmenu():
state={}
addplayers(state)
def addplayers(state):
state['player_info'] = {}
For some forms of programming, immutable state is far preferable (in particular, multithreading where data is shared, or to keep a log where you can undo steps). That's done similarly but you make a new state for each call:
def mainmenu():
state = {}
state = addplayers(state)
def addplayers(oldstate):
newstate = oldstate.copy()
newstate['player_info'] = {}
return newstate
Python isn't designed for this and doesn't really have a mode to keep you from inadvertently modifying mutable types. Some types can be converted to similar types that are immutable, like frozenset or tuple.
One of the weirder hacks we can do is calling a Python function with a different set of globals than it normally has. This can be abused to take your existing functions, global statements and all, and have them use a different variable:
fakeglobals = mainmenu.__globals__.copy()
exec(addplayers.__code__, fakeglobals)
Your original code has calls back and forth between functions, though, and each of those will reset their globals per that __globals__ attribute.
Your code also implements a loop using tail recursion. This is not optimized in Python and will run out of stack space eventually. In languages where tail recursion is optimized, you can pass along state as arguments continously and need not return it.
I am learning to code in Python.
I am creating a program that will perform unit conversions, however I have been stuck on this error for a good while:
NameError: name 'ini' is not defined
Here is the code:
a = ("Distance")
b = ("Time")
c = ("Volume")
d = ("Temp")
e = ("Weight")
def conversion_type(first):
if first == ("Distance"):
ini = input("How great a distance am I converting?\n")
elif first == ("Time"):
ini = input("How much time am I converting?\n")
elif first == ("Volume"):
ini = input("How great a volume am I converting?\n")
elif first == ("Temp"):
ini = input("How many degrees am I converting?\n")
elif first == ("Weight"):
ini = input("How much weight am I converting?\n")
else:
print("That not was an afformentioned dimension you dolt.")
def variable_type_converter(ini):
ini = float(ini)
print ("\n Welcome to the Convert-O-Matic\n==============================\n")
print ("I support the following dimensions:\n")
print ("%s, %s, %s, %s, and %s," % (a,b,c,d,e))
first = input("What kind of conversion would you like to do?\n")
conversion_type(first)
variable_type_converter(ini)
print("==========================================")
ini is not declared in the global scope, only inside of functions (it is used in conversion_type() and therefore implicitly declared there, and it is an argument to variable_type_converter()). But since it was not declared in the global scope, it does not exist there. If you want to set the value of ini in conversion_type() and have the value be usable elsewhere, declare a value for ini somewhere before you call conversion_type().
This answer has a good summary of Python's scoping rules.
Update: As MadWombat points out in comments, your variable_type_converter() function doesn't do anything. It takes one argument, called ini, it casts it as float and reassigns back to ini, but then it doesn't return a value. So when the variable_type_converter() function exits, the value is discarded and and the float cast is never used.
When defining your variables you should use a = "Distance". You should also check the conversion type using first == "Distance"
ini is not defined, is occurring because the function conversion_type(first) is returning a value that is not stored. Try:
# get conversion type
conversion_type_chosen = conversion_type(first)
# calculate value
converted_value = variable_type_convertor(conversion_type_chosen)
# print result
print "result: {}".format(converted_value)
So first let me say that I am a novice at Python and functions seem to be out of my comprehension for the moment but where I am having trouble is having 3 functions be able to call each other. Here is my code(yes I know it is terribly wrong but you should see where I am going):
def menu():
count=gearboxes
cost=subtotal
return subtotal
def quantity():
gearboxes=raw_input("How many gearboxes would you like to order? ")
return menu()
def subtotal(cost):
if (gearboxes<=10):
cost=gearboxes*100.0
print cost
elif (gearboxes>10 and gearboxes<20):
cost=(gearboxes-10)*80.0+1000.0
print cost
elif (gearboxes>20):
cost=(gearboxes-20)*70.0+1000.0+800.0
print cost
else:
print "wtf m8"
return menu()
def summary():
print "="*80
print "%60s %20f %20f" % ("motors",count,cost)
print "="*80
print quantity()
print subtotal(menu)
print summary(menu)
There is it and any help would be greatly appreciated if you could explain also kind of how functions call on each other.
Thanks!
fixed version(still working)
def quantity():
motors=raw_input("How many motors would you like to order? ")
gearboxes=raw_input("How many gearboxes would you like to order? ")
sensors=raw_input("How many sensor boards would you like to order? ")
return int(motors),int(gearboxes),int(sensors)
def subtotal(motors,gearboxes,sensors):
if motors<=10 and gearboxes<=15:
motorCost=motors*100
gearboxCost=gearboxes*50
sensorCost=sensors*66
return motorCost, gearboxCost, sensorCost
if motors>10 and motors<=20 and gearboxes>15 and gearboxes<=30:
motorCost=(motors-10)*80+1000
gearboxCost=(gearboxes-15)*40+750
sensorCost=sensors*66
return motorCost, gearboxCost, sensorCost
elif motors>20 and gearboxes>30:
motorCost=(motors-20)*70+1000+800
gearboxCost=(gearboxes-30)*30+750+600
sensorCost=sensors*66
return motorCost, gearboxCost, sensorCost
def summary(motors,gearboxes,sensors,motorCost,gearboxCost,sensorCost):
print "="*80
print "%60s %20d %20d" % ("motors",motors,motorCost)
print "%60s %20d %20d" % ("gearboxes",gearboxes,gearboxCost)
print "%60s %20d %20d" % ("sensor boards",sensors,sensorCost)
print "="*80
def menu():
a,b,c=quantity()
d,e,f=subtotal(a,b,c)
summary(a,b,c,d,e,f)
return
menu()
I made some changes to your code. Treat a function like a question. When you call the function; you're asking the question. What you pass to return is the answer to the question. So when someone asks for the subtotal of some number of gearboxes; we return cost, whatever that may be.
We can then store the return values (the answers) in variables and use them later. For example, to pass to another function. Try to follow how information flows through the program.
def quantity():
count=raw_input("How many gearboxes would you like to order? ")
return int(count)
def subtotal(count):
if count<=10:
cost=count*100.0
return cost
elif count>10 and count<20:
cost=(count-10)*80.0+1000.0
return cost
elif count>20:
cost=(count-20)*70.0+1000.0+800.0
return cost
def summary(count, cost):
print "="*80
print "%60s %20f %20f" % ("motors",count,cost)
print "="*80
def menu():
items = quantity()
sub = subtotal(items)
summary(items, sub)
if __name__ == '__main__':
menu()
"subtotal" already calls menu() so I'm not sure what you are asking since you are already calling one function within the other.
Also, I can't see what your program is supposed to do - if your function names would be verbs (print_menu, get_menu, set_menu, throw_menu_on_moon, calculate_subtotal, ...) it would be better to understand for humans.
Also, the names you use (on the right hand side of =) within a function must be known there, so for example
def menu():
count=gearboxes
makes no sense (because "gearboxes" is unknown - on the other hand, "count" is fine since it defines new variable - since it is on the left hand side of =)...
Note that variables are only known within the function you defined them in, so
def f():
gearboxes = 2
def menu():
count=gearboxes
would make no sense either.
But
def f():
return 2
def menu():
gearboxes=f()
count=gearboxes
would make perfect sense.
Read the
def calculate_subtotal(gearbox_count):
as "to calculate subtotal of gearbox count do".
If you then say anywhere outside:
calculate_subtotal(5)
^^^^^^^^^^^^^^^^^^^^^
then the underlined part will be replaced by the result returned.
Otherwise, in Python the lines (in a block) are executed one after another - if you want to do multiple things in sequence, you can just write them one line each, one after another.
"return" is not "goto", "return" gives back the result - and control - to the caller of the function. Then the result is placed into the program "instead of the call".