Globalising variables from sub-routine into another sub-routine - python

I am having some trouble for carrying my variable from a sub-routine into another sub-routine.
Here is the code:
def loop1():
try:
age=int(input("How old are you? "))
except ValueError:
print ("Please enter a numerical integer of your age. For example: 19 ")
print("")
loop1()
if age>0:
program()
def program():
print("")
print("[1] - Knife / Spray Paint / Lottery Ticket ")
print("[2] - Alcohol / Tobacco ")
print("[3] - Anything else ")
print("")
loop2()
def loop2():
try:
item=int(input("What would you like to buy from the options above? "))
print("")
except ValueError:
print ("Please enter a numerical integer of your item. For example (if you wanted to buy alcohol): 2 ")
print("")
loop2()
if item>0:
validation()
def validation():
if item == 1 and 16>age :
print("Sale Denied - Item cannot be sold to Under 16s. ")
elif item == 1 and 16<age:
print("Sale Accepted. ")
elif item == 2 and 18>age:
print("Sale Denied - Item cannot be sold to Under 18s. ")
elif item == 2 and 25>age>18:
print("Check ID before selling alcohol - Challenge 25. ")
elif item == 2 and 18<age:
print("Sale Accepted. ")
elif item == 3:
print("Sale Accepted. ")
loop1()
Here is the outcome:
How old are you? 21
[1] - Knife / Spray Paint / Lottery Ticket
[2] - Alcohol / Tobacco
[3] - Anything else
What would you like to buy from the options above? 2
Traceback (most recent call last):
File "D:/Shop Program.py", line 48, in <module>
loop1()
File "D:/Test.py", line 9, in loop1
program()
File "D:/Shop Program.py", line 17, in program
loop2()
File "D:/Shop Program.py", line 28, in loop2
validation()
File "D:/Shop Program.py", line 33, in validation
if item == 1 and 16>age :
NameError: global name 'item' is not defined
As you can see from the error message above it is saying that global name 'item' is not defined. I have tried to place global item, above def vaildation():, but I still get the same error.

Rather than using global, which is a bad practice (in Python and everywhere else), explicitly pass item from loop2 into validation:
def loop2(age):
...
if item > 0:
validation(item, age)
# ^ pass it here
def validation(item, age):
# ^ receive it here
if item == 1 and 16 > age:
...
Note that I have done a similar thing with age, which should be passed in when loop2 is called. Using recursion for input validation isn't ideal; see Asking the user for input until they give a valid response for an alternative approach.

Forgive me if you already know this, but there is another way to get the item into the validate sub-routine that might work better for you. You can "pass in" a variable to a subroutine (also called methods or functions). Variables that you "pass in" to a subroutine are called arguments.
To use subroutine arguments, you have to do 2 things:
Define the arguments in the subroutine definition
Specify the variable to "pass in" when you call the subroutine
So for you, defining the argument item on your validate routine would look like this:
def validate(item):
if item == 1 and 16>age :
See how I stuck item between the parenthesis.
And then, all you have to do is "pass in" the item to the validate function:
def loop2():
try:
item=int(input("What would you like to buy from the options above? "))
print("")
except ValueError:
print ("Please enter a numerical integer of your item. For example (if you wanted to buy alcohol): 2 ")
print("")
loop2()
if item>0:
validation(item)
Notice how I put item between the parenthesis on the call to the validation subroutine on the last line.
Hope that helps

Apart from your original question: There is the possibility for an infinite recursion in loop1 and loop2 if the exception is risen, as the functions call themselves.
A better way is to use a loop (while True: ...) with a break after successful conversion.
Also, it is bad practice in every programming language to chain functions the way you did. That is an even worse idea than using goto and is commonly called spaghetti code.
Better is to have a single main function which calls the functions in succession and passes results of a former function as arguments to the next function:
age = get_age()
item = get_item()
if age < 16 and item == ...:
print("Not allowed")
...
A better approach would be to use a dict of { "item" : minimal_age }:
items = { "butter": 0 , "knife": 16, "booze": 18, "beer": 17 }
age = ...
item = get_item(items) # the list can be built automatically from dict
if age < items[item]:
print("Too young for", item)
else:
purchase(item)
That would avoid a loooooong list of if .. elif .. tests.
If the items are identified by strings instead of numbers, that would add to readability. In general, in Python numberincal values should only be used where they are "natural", otherwise use the appropriate type like strings, sets, etc.
Note that this is different from other languages (e.g. C), where string handling is quite uncomfortable and expensive.

Related

Removing an item from the dictionary "grocery_list" causes an error

I seem to receive an error every time I try to remove an item from the dictionary. Here's my code:
print(" MY NEW AND IMPROVED GROCERY LIST ")
def grocerylist():
hist_grocery = []
grocery = True
while grocery:
choice = str(input("\n=====================\nWhat would you like to do? \n1 - Add an item\n2 - Remove an item "
"\n3 - Print entire list\n4 - Calculate cost\n5 - Exit program\nChoice: "))
if choice == "1":
print("=====================\nADD AN ITEM\n")
information = input("Give the following information: \nItem name: ")
price = input("Item price: ")
quantity = input("Item quantity: ")
grocery_list = {"Item name": str(information), "price": float(price), "quantity": int(quantity)}
hist_grocery.append(grocery_list)
** elif choice == "2":
print("=====================\nREMOVE AN ITEM\n")
remove_item = str(input("What would you like to remove? \nItem name: ")).format() # format function
grocery_list = [i for i in hist_grocery if str(i).lower() != remove_item]
**
elif choice == "3":
print("=====================\nPRINTING LIST...")
if hist_grocery == []:
print("The grocery list is empty!")
else:
[print(items, end="\t ") for items in grocery_list.values()]
Here's what I inputted: enter image description here
Tried removing the item egg but it became an error.
Here's what the terminal says:
enter image description here
I tried creating another for loop but somehow I got confused along the process. What should I change in my code?
Your error isn't with deleting an item it's with printing the items.
Since you didn't choose option 1 first there is no existing object grocery_list. I'm speculating a bit here but I assume the interpreter sees the for-each loop and assumes that it must be a list, which doesn't have a .values() method so it gives you this error.
I would try declaring an empty dictionary before your first if block.
The error has almost nothing to do with the "delete" option. The error is in the "print" option. If you look in the code for choice 2, you see that grocery_list = [i...], which is definitely a list. As the error clearly states, you cannot use .values() with a list, like when you tried to do so in the 3rd option, where [...for items in grocery_list.values()].
PS: Please don't post your error as a picture. It makes it easier for all those involved if it's typed out in the actual question.

Why does my code keep on looping like this when I'm using functions?

So I'm trying to make a Hangman console game and im trying to check how many tries I have left and then later add a game over function if tries left. One part of it is working the stringcount function is working properly until I have to call the goAgain function, I'll explain the issue lower in the document
import time
hangmanSolved = "What 'eva hh"
chosingletter = input("Input a letter you would like to try! ")
def goAgain(defaultTries):
defaultTries -= 1
print("Head drawn!", defaultTries, "tries left ")
stringcount()
# if a letter is in, and how many letters are in
def stringcount():
count = 0
lettersIn = hangmanSolved.count(chosingletter)
for i in hangmanSolved:
if i == chosingletter:
count = count + 1
if not count:
print("There are no ", chosingletter, "'s in this sentence! ")
time.sleep(1)
goAgain(defaultTries=5)
elif count == 1:
print("There is only one ", chosingletter, " in this sentence! ")
else:
print("There is ", lettersIn, chosingletter, "'s in this sentence! ")
stringcount()
When I run the code and guess a wrong letter, or rather when I have to call goAgain function it keeps on looping me this output:
Input a letter you would like to try! j
There are no j 's in this sentence!
Head drawn! 4 tries left
There are no j 's in this sentence!
Head drawn! 4 tries left
How can I fix this looping, and the tries counter?!?
So first of all the counter is wrong because you are using a local variable. When the method is called with defaultTries = 5 it will always start at 5 even though you have done defaultTries -= 1.
To fix this you need to read up and assign a global variable outside of the scope so that whenever the user is wrong that global variable gets subtracted.
Furthermore, I am unsure as to why you would use the time module here. What is your goal by doing this?
What you are trying to do is loop through the sentence and check if the letter the user inputs exists inside the sentence. There are easier ways to go about this.
You can use string.indexOf('a'). where 'a' you can change it to the letter you are searching for (the user's input). It returns the index of the first occurrence of the character in the character sequence represented by this object, or -1 if the character does not occur.
I would start with changing those things first!
Every time you call goAgain you call it with defaultTries set to 5. Then, in that function, it subtracts one, leaving you with 4. Then it calls stringount which again calls goAgain which will call stringcount and so on. There's nothing that breaks out of the loop.
So you need to change three things:
Don't pass in the same value of defaultTries every time you call goAgain. You want to allow that variable to decrease.
Git rid of the goAgain function. Instead put that logic in stringcount itself.
Check the value of the number of tries remaining and if it's zero, print out some kind of message, but don't call stringcount in that branch.
def stringcount(tries = 5):
count = 0
tries =- 1
if tries == 0:
print("No tries remaining")
else:
lettersIn = hangmanSolved.count(chosingletter)
for i in hangmanSolved:
if i == chosingletter:
count = count + 1
if not count:
print("There are no ", chosingletter, "'s in this sentence! ")
time.sleep(1)
stringcount(tries)
elif count == 1:
print("There is only one ", chosingletter, " in this sentence! ")
else:
print("There is ", lettersIn, chosingletter, "'s in this sentence! ")
stringcount()
A few other things you can consider changing:
The standard formatting in Python is PEP8 and you should review those. For example, Python convention is to use snake_case instead of camelCase. So you would use something like default_tries instead of defaultTries for example.
It's probably more explicit to check if count == 0 than it is to check if not count.
I think you don't need the loop that updates the variable count. Just use lettersIn (which you should rename as letters_in or, better, just call this count). Now you also don't need to set count==0 at the top of the function.
You're having problems because you are basically using a recursive strategy but you haven't defined a base case, and also because you reinitialize your defaultTries variable to 5 every time you call the goAgain() function. A base case is a condition where you tell a recursive function to stop. In this case tries needs to be 0. Here's an example of what your code might look like:
def goAgain(tries):
tries -= 1
if tries==0:
print("You lose")
return None
print("Head drawn!", tries, "tries left ")
stringcount(tries)
def stringcount(defaultTries=5): #This is how you set default args
chosingletter = input("Input a letter you would like to try! ")
count = 0
lettersIn = hangmanSolved.count(chosingletter)
for i in hangmanSolved:
if i == chosingletter:
count = count + 1
if not count:
print("There are no ", chosingletter, "'s in this sentence! ")
time.sleep(1)
goAgain(defaultTries)
It's up to you whether you want to keep goAgain() and stringcount() separate. I think it makes sense to keep them separate because I can imagine you want to check how many tries are in goAgain and then print new statements accordingly, like "left arm drawn!" etc.

Method returns None even if value is not none in python 3 [duplicate]

This question already has answers here:
Why does my recursive function return None?
(4 answers)
Closed 3 years ago.
I'm getting input at run time. If first attempt itself correct input given means I'm not getting the error. Otherwise it is showing can't unpack non-iterable error.
I can see it is sending None if recursive method called once. Even it prints value before return, but after receiving it shows None.
class Game:
def set_val(self):
try:
p1=input("Enter player 1 name:")
p2=input("Enter player 2 name:")
goal=int(input("Enter a number to set goal:"))
if p1 is None or p2 is None or goal is None:
print("Please give proper input")
self.set_val()
else:
print(p1,p2,goal)
return p1,p2,goal
except:
print("Please give proper input")
self.set_val()
G=Game()
p1,p2,goal=G.set_val()
print(p1,p2,goal)
OUTPUT:
Enter player 1 name:s
Enter player 2 name:c
Enter a number to set goal:s
Please give proper input
Enter player 1 name:s
Enter player 2 name:v
Enter a number to set goal:2
s v 2
Traceback (most recent call last):
File "D:\test.py", line 18, in <module>
p1,p2,goal=G.set_val()
TypeError: cannot unpack non-iterable NoneType object
I can assume it because of recursive calls, but not able to figure it out why. Please explain give a solution to get all input values without error.
Recursion has its uses, but using it to start over is not one of them. Naked except is also bad.
If you need to loop, use a loop.
If you need to catch an exception, catch the expected exception. That way if an unexpected exception occurs, you can see and deal with the bug.
Example:
class Game:
def set_val(self):
while True:
p1=input("Enter player 1 name:")
if p1: break
while True:
p2=input("Enter player 2 name:")
if p2: break
# Loop until good input
# int() will throw ValueError if input is not convertible to int.
while True:
try:
goal=int(input("Enter a number to set goal:"))
break # break from the loop if no exception
except ValueError:
print("Please give proper input")
return p1,p2,goal
G=Game()
p1,p2,goal=G.set_val()
print(p1,p2,goal)
1) Never do a general exception to catch everything.
2) You should NOT use recursion for user input. It consumes stack for no reason. Just use a while loop. Basically recursion is not designed to keep repeating if something fails. It is designed to dive into something and eventually come back.
3) Make sure you properly space your code. This didn't cause the issue but it does make the code hard to read. You should have a space before and after operators (i.e. =) and a space after commas with multiple elements (i.e. (1, 2, 3))
That being said, when you do recursion, you need to return the recursive call:
class Game:
def set_val(self):
try:
p1 = input("Enter player 1 name:")
p2 = input("Enter player 2 name:")
goal = int(input("Enter a number to set goal:"))
if p1 is None or p2 is None or goal is None:
print("Please give proper input")
return self.set_val()
else:
print(p1, p2, goal)
return p1, p2, goal
except:
print("Please give proper input")
return self.set_val()
G = Game()
p1, p2, goal = G.set_val()
print(p1, p2, goal)
Also your if can be shortened to this:
if p1 and p2 and goal:

Problem with Repeating a Function in Mastermind Game

I am designing a mastermind game to be played with python. But I encounter some problems when I try to set a function to repeat itself when the attempts are not completely correct.
My code is in two parts. For the first part it asks the user for the correct number, and then the second user tries to input his attempt number. The second part of the code breaks down his attempt into lists of numbers, and compute the number of correct integers and number of integers in correct position, then if the answer is not completely correct, the programme asks the user for a second input.
def getnumber():
predestine = input("Please input your test number")
a = str(predestine)
attempt()
def attempt():
attempt = input("Attempt:")
b = str(attempt)
correctrecord = []
sequencerecord = []
for i in b:
if i in a:
correctrecord.append(1)
for i in range(0,4):
if b[i] == a[i]:
s equencerecord.append(1)
correctlength = len(correctrecord)
sequencelength = len(sequencerecord)
print(f"You have made {correctlength} correct attempts, and of these {sequencelength} are of correct positions")
if sequencelength == 4:
print("You have won, the game is ended")
else:
return attempt()
The problem is with the last code: return attempt(). It seems it fails to repeat the function with 'str object not callable' error.
The problem in your code lies in variable shadowing.
Your repeated function is in a variable named attempt, a global variable. Then, inside the attempt function you define an attempt string variable, local to this function, and therefore temporarily shadowing the global attempt variable that held the function.
Therefore, the call attempt() fails, as you're essentially trying to call a string.
The solution would be to rename the local string variable attempt to not shadow the global one:
def attempt():
attempt_ = input("Attempt:")
b = str(attempt_)
correctrecord = []
sequencerecord = []
for i in b:
if i in a:
correctrecord.append(1)
for i in range(0,4):
if b[i] == a[i]:
sequencerecord.append(1)
correctlength = len(correctrecord)
sequencelength = len(sequencerecord)
print(f"You have made {correctlength} correct attempts, and of these {sequencelength} are of correct positions")
if sequencelength == 4:
print("You have won, the game is ended")
else:
return attempt()
Your use same variable-names multiple times. Python functions are first class citizens, which allows you to do:
# define a function by name r
def r():
return 1
print(type(r)) # <class 'function'> now r is a function
# reassign name r to be a string
r = "22"
print(type(r)) # <class 'str'> now r is a string
If you do r() now you get TypeError: 'str' object is not callable
Your code uses global variables and you call your own function again and avain - this can lead to recursion overflow - see What is the maximum recursion depth in Python, and how to increase it?
You will get wrong results when calculating the amount of correct "digits" when there are duplicates - try "1122" as correct value and "1234" as attempt.
Recursion is not needed to code your game. I restructured it a bit to showcase a different way:
def getnumber(text):
"""Loops until a number is inputted. Returns the number as string.
'text' is the prompt for the user when asking for a number."""
while True:
try:
predestine = int(input(text).strip())
return str(predestine)
except:
print("Only numbers allowed!")
def attempt(correct_number):
"""One game round, returns True if correct result was found."""
attempt = getnumber("Attempt: ")
# avoid double-counting for f.e. 1212 and 1111 inputs
correct_digits = len(set(attempt) & set(correct_number))
sequencelength = 0 # no need to collect into list and use len
for i,c in enumerate(attempt): # simply increment directly
if c == correct_number[i]:
sequencelength += 1
print(f"You have found {correct_digits} correct digits, and of these {sequencelength} are of correct positions.")
if len(attempt) < len(correct_number):
print("Your number has too few digits.")
elif len(attempt) > len(correct_number):
print("Your number has too many digits.")
return correct_number == attempt
# game - first get the correct number
number = getnumber("Please input your test number: ")
# loop until the attempt() returns True
ok = False
while not ok:
ok = attempt(number)
print("You have won, the game is ended")
Output:
Please input your test number: 1234
Attempt: 1111
You have found 1 correct digits, and of these 1 are of correct positions.
Attempt: 1212
You have found 2 correct digits, and of these 2 are of correct positions.
Attempt: 1321
You have found 3 correct digits, and of these 1 are of correct positions.
Attempt: 1234
You have found 4 correct digits, and of these 4 are of correct positions.
You have won, the game is ended

using exceptions and writing data to files in python 3

here what I need to do:
Your program must raise an exception if the user chooses any item not on the menu
presented. Along with raising an exception, write the code to handle this exception.
Ask the user for a value to convert.Your program must raise and exception, and handle the exception, if an input
errors occurs
Perform the conversion and write the original value, the original unit, the
converted value, and the converted unit to an output file named
conversions.txt.
Repeat steps a and b 10 times (in a loop).
heres my code:
#imports
import os
# global variables
mile_choice = 1
gallon_choice = 2
pound_choice = 3
inch_choice = 4
fah_choice = 5
quit_choice = 6
mainfile = open('conversions.txt', 'w')
# intro and global name variable
name = input ('what is your name? ')
print()
print('hello',name,', today we will be doing\
some standard to metric conversions.')
#define main function
def main():
choice = 0
while choice != quit_choice:
display_menu()
print()
choice = int(input('Please enter a number 1 - 6 : '))\
if choice == mile_choice:
print()
miletokm()
elif choice == gallon_choice:
print()
galtolit()
elif choice == pound_choice:
print()
poundstokg()
elif choice == inch_choice:
print()
inchtocm()
elif choice == fah_choice:
print()
fahtocel()
elif choice == quit_choice:
print()
print('Exiting the program.')
#define functions
def display_menu():
print()
print(' Menu ')
print()
print('Press 1 for Miles to Kilometers')
print()
print('Press 2 for Gallons to Liters')
print()
print('Press 3 for Pounds to Kilograms')
print()
print('Press 4 for Inches to Centimeters')
print()
print('Press 5 for Fahrenhiet to Celisus')
print()
print('To exit please enter 6 ')
def miletokm():
invalid_attempts = 0
#while loop for invalid input limited to 3
while invalid_attempts < 3 and invalid_attempts >= 0:
print()
mile = float(input('how many miles would you\
like to convert to kilometers? '))
mainfile.write(str(mile) + '\n')
# if statement to determine weather to proceed with conversation
# valid input = conversion
# invalid input = return with + 1 to invalid_attempt count
if mile >= 0 :
print()
mile_conv = mile * 1.6
print('that would be:', format(mile_conv, '.2f'), 'kilometers.')
print()
mainfile.write(str(mile_conv) + '\n')
return mile
else:
print()
print ('invalid input')
print()
invalid_attempts += 1
I left out the other conversion def. to help keep it shorter.
I am having problems with the exception part first and for most.
I have tried various things but I cant figure out how to write out the code correctly
I know how to define a value error for a number entered outside of the menu range
I don't understand how to write the units along with the data entered to the file.
The way I Have it now, it is not writing any information to mainfile.
I also feel like my code is very sloppy written. I have no idea because my professor refuses to help me.
I know that's alot to run through but i really have no where else to turn. I don't understand how I should structure the code and how to effectively accomplish what I need done.
what I have read covers the basis of this but I have no examples to look at other than very simple simple examples that deal with strictly one thing.
You could try something like... (from http://docs.python.org/2/tutorial/errors.html#exceptions)
>>> while True:
... try:
... x = int(raw_input("Please enter a number: "))
... break
... except ValueError:
... print "Oops! That was no valid number. Try again..."
...
You're on the right track. First thing that you need to do is to handle better the value for choice that the user gives you. Check what happens if they give you 9 or 'foo'.
Next, you should do the same for every value received in your functions that convert units. For that, you use try/except as #bitfish showed you (except that you use input instead of raw_input).
close the files you open (mainfile.close())
doing this elif choice == quit_choice: inside of this while choice != quit_choice makes no sense
use '\n' to skip lines (print('\n') is the same than print() two times
there are many ways to solve such a problem, white the experience you'll acquire you'll find more elegant ones, but this one is already ok.

Categories

Resources