this is my current code, however the the input doesn't change to an integer
can someone help me pls
score = [0,20,40,60,80,100,120]
def validate_credits(input_credits):
try:
input_credits = int(input_credits)
except:
raise ValueError('integer required')
if input_credits not in score:
raise ValueError('out of range')
while True:
try:
mark1 = input('Enter your total PASS marks: ')
validate_credits(mark1)
mark2 = input('Enter your total DEFER marks: ')
validate_credits(mark2)
mark3 = input('Enter your total FAIL marks: ')
validate_credits(mark3)
except ValueError as e:
print(e)
This is a scoping issue. Consider:
def the_function(_the_input):
_the_input = int('2')
if __name__ == "__main__":
the_input = '1'
the_function(the_input)
print(the_input)
What do you think the_input will be? '1' or 2?
Output is '1'
How about a list?
def the_function(_the_input):
_the_input.append('2')
if __name__ == "__main__":
the_input = ['1']
the_function(the_input)
print(the_input)
Output is ['1', '2']
At the risk of not explaining this correctly, I'd instead refer you to this thread on the topic
To get your input as an integer, you need to return the value:
def the_function(_the_input):
# + other function logic...
return int(_the_input)
if __name__ == "__main__":
the_input = '1'
the_input = the_function(the_input)
print(the_input)
Related
I am trying to unit test my program and have decided to unit test the withdraw_cash function. However, it is called by the bank_atm function. I have never called a function that is dependent on another function and am confused about how to do this. Would i use mock and patch to do this?
The tests would be:
check whether entering valid amount
check whether amount is less than balance
user = {
'pin': 1234,
'balance': 100
}
def withdraw_cash():
while True:
try:
amount = int(input("Enter the amount of money you want to withdraw: "))
except ValueError as v:
print(f"Enter correct amount: ")
else:
if amount > user['balance']:
raise ValueError("You don't have sufficient balance to make this withdrawal")
else:
user['balance'] = user['balance'] - amount
print(f"£{user['balance']}")
return
finally:
print("Program executed")
def bank_atm():
count = 0
to_exit = False
while (count < 3) and (not to_exit):
try:
pin = int(input('Please enter your four digit pin: '))
except ValueError:
print("Please enter correct pin")
count += 1
if pin != user['pin']:
print("Pin does not match.. Try Again")
count += 1
else:
withdraw_cash()
to_exit = True
if count == 3:
print('3 UNSUCCESFUL PIN ATTEMPTS, EXITING')
print('!!!!!YOUR CARD HAS BEEN LOCKED!!!!!')
try:
bank_atm()
except ValueError as v:
print(f"ERROR: {v}")
To expand on the comments. Put UI interactions in separate functions, such as getpin and getpounds. They can then be tested separately from the business functions. One can either mix manual input with the unittests or automate them by patching sys.stdin/out. My proof-of-concept experiment, which passes on 3.11.0b3 run from IDLE editor.
import sys
import unittest
from unittest.mock import patch
def getpin(realpin):
return input('Your pin? ') == realpin
class ManualTest(unittest.TestCase):
def test_pin(self):
print('\n(Enter 1234)')
self.assertEqual(getpin('1234'), True)
print('\n(Enter 1)')
self.assertEqual(getpin('1234'), False)
class MockOut:
def write(self, string): pass
# Or 'write = Mock()' to save and check prompts, using mockout.
class MockIn:
def readline(self): # input() uses readline, not read.
return self.line
#patch('sys.stdin', new_callable=MockIn)
#patch('sys.stdout', new_callable=MockOut)
class AutoTest(unittest.TestCase):
def test_pin(self, mockout, mockin):
mockin.line='1234\n' # input removes '\n' if present.
self.assertEqual(getpin('1234'), True)
mockin.line='1233'
self.assertEqual(getpin('1234'), False)
if __name__ == '__main__':
unittest.main(verbosity=2)
I have now refactored my program, by breaking it down into separate functions as you said but when I try and run a test specifically for one function, it runs the whole thing and asks for input from each function. I feel like testing each function individually will have to require specific parameters not dependent on the other function.
user = {
'pin': 1234
}
def withdraw_cash(amount):
balance_account = 100
if amount > balance_account:
raise ValueError("You don't have sufficient balance to make this withdrawal")
else:
new_balance = balance_account - amount
return new_balance
def get_pin():
count = 0
to_exit = False
while (count < 3) and (not to_exit):
try:
pin = int(input('Please enter your four digit pin: '))
except ValueError:
print("Please enter correct pin")
count += 1
if pin != user['pin']:
print("Pin does not match.. Try Again")
count += 1
else:
return get_amount(pin)
#return pin
if count == 3:
a = '3 UNSUCCESFUL PIN ATTEMPTS, EXITING \n !!!!!YOUR CARD HAS BEEN LOCKED!!!!!'
return a
def get_amount(pin):
while True:
try:
amount = int(input("Enter the amount of money you want to withdraw: "))
except ValueError as v:
print(f"Enter correct amount: ")
else:
#print(amount)
return withdraw_cash(amount)
#return amount
try:
get_pin()
except ValueError as v:
print(f"ERROR: {v}")
I want to get 10 random questions from an web API with many questions, but I dont seem to get it to work. Right now im getting KeyError: 'prompt', but I dont know if the function is correct at all as I have been trying a few diffrent options.
Im also trying to print out in the end which questions you get wrong, but with no luck there either.
import random
import requests
from random import randint
url = ""
the_questions = requests.get(url).json()
print("------ Welcome to Python quiz ------")
def random_question():
data = the_questions['prompt']
random_index = randint(0, len(data)-1)
return data[random_index]['prompt']
def get_correct_answers(answers):
res = []
for ans in answers:
if ans['correct']:
res.append(ans['answer'])
return res
def get_numeric(prompt, max_value,):
while True:
try:
res = int(input(prompt))
except ValueError:
print("Answer only with a number!")
continue
if 0 < res < max_value:
break
else:
print("Invalid answer option!")
return res
def main():
score = 0
for questions in the_questions['questions']:
#print(questions['prompt'])
print(random_question())
for i, a in enumerate(questions['answers'], start=1):
print(f"[{i}] {a['answer']}")
user_answer = get_numeric("> ", len(questions['answers']) + 1)
if questions['answers'][user_answer - 1]['correct']:
score += 1
print(f"Right!")
else:
all_correct = ", ".join(get_correct_answers(questions['answers']))
print(f"Wrong, right is: {all_correct}")
print(f"You got {score} points of {len(the_questions['questions'])} possible!")
if __name__ == '__main__':
main()
Sample of the API
{"questions":[{"id":"1","prompt":"Which functions is used to write out text in the terminal?","answers":[{"answer":"print","correct":true},{"answer":"input","correct":false},{"answer":"import","correct":false},{"answer":"sys.exit","correct":false}]}
Error shows problem with key prompt and you use it only in random_question().
If you use print() to see values in variables random_question() then you will see that you need
data = the_questions['questions']
instead of
data = the_questions['prompt']
As for me name of variables are missleading. You should use name data for all information from API and later questions = data['questions'] could make sense.
EDIT:
My version with random.shuffle()
import random
#import requests
import json
def get_correct_answers(answers):
res = []
for ans in answers:
if ans['correct']:
res.append(ans['answer'])
return res
def get_numeric(prompt, max_value,):
while True:
try:
res = int(input(prompt))
except ValueError:
print("Answer only with a number!")
continue
if 0 < res < max_value:
break
else:
print("Invalid answer option!")
return res
def main():
print("------ Welcome to Python quiz ------")
#url = ""
#data = requests.get(url).json()
data = json.loads('''
{"questions":[
{"id":"1","prompt":"Which functions is used to write out text in the terminal?","answers":[{"answer":"print","correct":true},{"answer":"input","correct":false},{"answer":"import","correct":false},{"answer":"sys.exit","correct":false}]},
{"id":"2","prompt":"Which functions is used to read text from the terminal?","answers":[{"answer":"print","correct":false},{"answer":"input","correct":true},{"answer":"exec","correct":false},{"answer":"load","correct":false}]}
]}
''')
questions = data['questions']
random.shuffle(questions)
score = 0
for question in questions:
print(question['prompt'])
answers = question['answers']
for i, a in enumerate(question['answers'], 1):
print(f"[{i}] {a['answer']}")
user_answer = get_numeric("> ", len(answers)+1)
if answers[user_answer-1]['correct']:
score += 1
print(f"Right!")
else:
all_correct = ", ".join(get_correct_answers(answers))
print(f"Wrong, right is: {all_correct}")
print(f"You got {score} points of {len('questions')} possible!")
if __name__ == '__main__':
main()
def is_digit(x):
if type(x) == int:
return True
else:
return False
def main():
shape_opt = input('Enter input >> ')
while not is_digit(shape_opt):
shape_opt = input('Enter input >> ')
else:
print('it work')
if __name__ == '__main__':
main()
So when the user input a value that is not an integer, the system will repeat the input(). Else, it does something else. But it won't work, may I know why?
Check this. Input always returns a string. So isdigit() is better to use here. It returns True if all characters in a string are digits and False otherwise.
return x.isdigit() will evaluate to True/False accordingly, which will be returned
def is_digit(x):
return x.isdigit()
def main():
shape_opt = input('Enter input >> ')
while not is_digit(shape_opt):
shape_opt = input('Enter input >> ')
else:
print('it work')
if __name__ == '__main__':
main()
An easy way to test if an string is an int is to do this:
def is_digit(x):
try:
int(x)
return True
except ValueError:
return False
You must use try except when trying to convert to int.
if it fails to convert the data inside int() then throw the exception inside except which in your case with makes the loop continue.
def is_digit(x):
try:
int(x)
return True
except:
return False
def main():
shape_opt = input('Enter input >> ')
while not is_digit(shape_opt):
shape_opt = input('Enter input >> ')
else:
print('it work')
if __name__ == '__main__':
main()
I'm doing a school project and the user will need to input some values (1, 2 or 3 and their combinations separated by comma). Ex: 2,3,1 or 2,1
I need to prevent the user from typing anything else out of the standard value.
Here's my attempt, which is working, but looks very dumb. Anyone could think somehow to improve it?
while True:
order = input("Input value: ")
try:
if order == "1" or order == "2" or order == "3" or order == "1,2" or order == "1,3" or \
order == "2,3" or order == "2,1" or order == "3,1" or order == "3,2" or order == "1,2,3" \
or order == "1,3,2" or order == "2,1,3" or order == "2,3,1" or order == "3,2,1" or order == "3,1,2":
list_order = order.split(",")
break
else:
print("\nError. Try again!\n")
continue
except:
pass
print(list_order)
Instead of waiting to .split() the order until after checking the order components are correct, do it beforehand. Then, make a set out of that, and check whether it's a subset of the correct/acceptable values. Using a set means that order doesn't matter for this check.
while True:
order = input('Input Value: ')
list_order = order.split(',')
try:
if set(list_order) <= set(['1', '2', '3']):
break
else:
print("\nError. Try again!\n")
continue
except:
pass
print(list_order)
why dont you make a list of possible inputs and check if input for user is present in that list?
inputList = []
inputList.append("1")
inputList.append("2")
inputList.append("3")
inputList.append("1,2")
inputList.append("1,3")
inputList.append("2,3")
inputList.append("2,1")
inputList.append("3,1")
inputList.append("3,2")
inputList.append("1,2,3")
inputList.append("1,3,2")
inputList.append("2,1,3")
inputList.append("2,3,1")
inputList.append("3,2,1")
inputList.append("3,1,2")
while True:
order = input("Input value: ")
try:
if order in inputList:
list_order = order.split(",")
break
else:
print("\nError. Try again!\n")
continue
except:
pass
print(list_order)
while True:
order = input("Input value: ")
try:
if "," in order:
list_order = order.split(",")
list_numbers = []
while list_order:
clipboard = list_order[0]
if clipboard != "1" or clipboard != "2" or clipboard != "3":
del list_order[0]
elif clipboard == "1" or clipboard == "2" or clipboard == "3":
list_numbers.insert(-1, clipboard)
del list_order[0]
print(list_numbers)
print(list_order)
break
else:
print("\nError. Try again!\n")
except:
pass
This isn't the best result. But is it a good example for testing every letter by letter. :) I hope I could help you, and it not, maybe you learned something new :)
# In case your needs should ever change (for example also allowing '-')
# You can put anything you want int this list.
valid_characters = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', ',']
while True:
order = input("Input value: ")
try:
# we use `set()` to get each unique character. For example:
# "1,2,33,4516,14" would just be {'1', '6', '3', '4', '2', '5', ','}
for char in set(order):
if char not in valid_characters:
# raise an exception of type ValueError
raise ValueError
# The exception gets handled here
except ValueError:
print("\nError. Try again!\n")
# And we go back to the top
continue
list_order = order.split(',')
print(list_order, end="\n\n")
Let me know if you have any questions or if some parts are not clear enough.
This code does what you want.
import re
def isValid(order):
if(re.fullmatch('[1-3]([ \t]*,[ \t]*[1-3])*', order)):
print("true")
return True
else:
return False
while True:
order = input("Input value: ")
try:
if isValid(order):
list_order = order.split(",")
break
else:
print("\nError. Try again!\n")
continue
except:
pass
print(list_order)
I created a function that uses regular expressions to check validity.
This is another answer similar to my previous answer in which you told that what if support numbers goes to 9. So here is the solution for that using itertools library.
you can change the for loop range as per you requirement
from itertools import permutations
inputList = []
for i in range(1,10)
perm = permutations(["1", "2", "3","4","5","6","7","8","9"],i)
for i in list(perm):
inputList.append(','.join(list(i)))
while True:
order = input("Input value: ")
try:
if order in inputList:
list_order = order.split(",")
break
else:
print("\nError. Try again!\n")
continue
except:
pass
print(list_order)
I want to be able to have a program whereby the user can input a paragraph/sentence/word/character whatever and have that stored in a list e.g. in list[0]. Then I want them to be able to write another bit of text and have that stored in e.g. list[1]. Then at any time I want the user to be able to read that from the list by choosing which segment they want to read from e.g. reading "hello" from list[0] whilst in list[1] "hi" is stored. Then when the user exits the program I want the list to be written to an external file. Then, at next start up, the program should read the file contents and store it again in the list so that the user can add more bits of text or read the current bits. When the list is saved to a file it should append new or changed parts but overwrite parts that are the same so as not to have duplicates. I have attempted this without much success. I am to be honest not sure if it is possible. I have browsed similar forums and have found that hasn't helped much so here it is.
My code so far:
import os
import time
import csv
global write_list
global f1_contents
write_list = []
def write():
os.system("cls")
user_story = input("Enter your text: \n")
write_list.append(user_story)
def read():
os.system("cls")
user_select_needs = True
while user_select_needs == True:
user_select = input("Enter the list section to read from or type exit: \n")
if user_select == "exit":
user_select_needs = False
try:
int(user_select)
select = user_select
select = int(select)
try:
print(write_list[select])
user_select_needs = False
enter = input("Press enter:")
except:
print("There is not stored data on that section!")
except ValueError:
print("That is not a valid section!")
def exit():
os.system("cls")
max_num_needs = True
while max_num_needs == True:
set_max_num = input("Set the storage: \n")
try:
int(set_max_num)
max_num = set_max_num
max_num = int(max_num)
max_num_needs = False
except:
print("It must be an integer!")
for i in range(0, max_num):
f = open("Saves.txt", "a")
f.write(write_list[i])
f.close()
os._exit(1)
def main():
store_num_needs = True
while store_num_needs == True:
set_store_num = input("State the current storage amount: \n")
try:
int(set_store_num)
store_num = set_store_num
store_num = int(store_num)
store_num_needs = False
except:
print("It must be an integer!")
try:
f1 = open("Saves.txt", "r")
for i in range(0, store_num+1):
i, = f1.split("#")
f1.close()
except:
print("--------Loading-------")
time.sleep(1)
while True:
os.system("cls")
user_choice = ""
print("Main Menu" + "\n" + "---------")
print("1) Write")
print("2) Read")
print("3) Exit")
while user_choice not in ["1", "2", "3"]:
user_choice = input("Pick 1, 2 or 3 \n")
if user_choice == "1":
write()
elif user_choice == "2":
read()
else:
exit()
if __name__ == "__main__":
main()
It might be too complicated to understand in which case just ask me in comments- otherwise general tips would be nice aswell.
Thanks in advance
A quick point of correction:
global is only required if you're defining a global variable inside a non-global context. In other words, anything defined at the default indentation level, will be accessible by everything else defined below it. For example:
def set_global():
x = 1
def use_global():
x += 1
set_global()
try:
use_global()
except Exception as e:
# `use_global` doesn't know
# about what `set_global` did
print("ERROR: " + str(e))
# to resolve this we can set `x` to a
# default value in a global context:
x = 1
# or, if it were required, we
# could create a global variable
def make_global():
global x
make_global()
# either will work fine
set_global()
use_global()
print(x) # prints 2
Now to the actual question:
I haven't read through the block of code you wrote (probably best to trim it down to just the relevant bits in the future), but this should solve the problem as I understand it, and you described it.
import os
import sys
user_text = []
# login the user somehow
user_file = 'saves.txt'
def writelines(f, lines):
"""Write lines to file with new line characters"""
f.writelines('\n'.join(lines))
def readlines(f):
"""Get lines from file split on new line characters"""
text = f.read()
return text.split('\n') if text else []
class _Choice(object):
"""Class that is equivalent to a set of choices
Example:
>>> class YesObj(Choice):
>>> options = ('y', 'yes')
>>> Yes = YesObj()
>>> assert Yes == 'yes'
>>> assert Yes == 'y'
>>> # assertions evaluate to True
Override the `options` attribute to make use
"""
allowed = ()
def __eq__(self, other):
try:
s = str(other)
except:
raise TypeError("Cannot compare with non-string")
else:
return s.lower() in self.allowed
def _choice_repr(choices):
allowed = []
for c in choices:
if isinstance(c, _Choice):
allowed.extend(c.allowed)
else:
allowed.append(c)
if len(allowed) > 2:
s = ', '.join([repr(c) for c in allowed[:-1]])
s += ', or %s' % repr(allowed[-1])
elif len(allowed) == 1:
s = '%s or %s' % allowed
else:
s = '%s' % allowed[0]
return s
def _choice_sentinel(name, allowed):
"""Creates a sentinel for comparing options"""
return type(name, (_Choice,), {'allowed': list(allowed)})()
Quit = _choice_sentinel('Quit', ('q', 'quit'))
Yes = _choice_sentinel('Yes', ('y', 'yes'))
No = _choice_sentinel('No', ('n', 'no'))
def readline_generator(f):
"""Generate a file's lines one at a time"""
t = f.readline()
# while the line isn't empty
while bool(t):
yield t
t = f.readline()
def read_from_cache():
"""Overwrite `user_text` with file content"""
if not os.path.isfile(user_file):
open(user_file, 'w').close()
globals()['user_text'] = []
else:
with open(user_file, 'r') as f:
lines = readlines(f)
# replace vs extend user text
for i, t in enumerate(lines):
if i == len(user_text):
user_text.extend(lines[i:])
else:
user_text[i] = t
def write_to_cache():
"""Overwrite cache after the first line disagrees with current text
If modifications have been made near the end of the file, this will
be more efficient than a blindly overwriting the cache."""
with open(user_file, 'r+') as f:
i = -1
last_pos = f.tell()
# enumerate is a generator, not complete list
for i, t in enumerate(readline_generator(f)):
if user_text[i] != t:
# rewind to the line before
# this diff was encountered
f.seek(last_pos)
# set the index back one in
# order to catch the change
i -= 1
break
last_pos = f.tell()
# then cut off remainder of file
f.truncate()
# recall that i is the index of the diff
# replace the rest of it with new
# (and potentially old) content
writelines(f, user_text[i+1:])
def blind_write_to_cache():
"""Blindly overwrite the cache with current text"""
with open(user_file, 'w') as f:
writelines(f, user_text)
def overwrite_user_text(i, text, save=False):
"""Overwrite a line of text
If `save` is True, then these changes are cached
"""
try:
user_text[i] = text
except IndexError:
raise IndexError("No text exists on line %r" % (i+1))
if save:
write_to_cache()
def user_input():
"""Get a new line from the user"""
return raw_input("input text: ")
def user_choice(msg, choices):
if len(choices) == 0:
raise ValueError("No choices were given")
ans = raw_input(msg)
if ans not in choices:
print("Invalid Response: '%s'" % ans)
m = "Respond with %s: " % _choice_repr(choices)
return user_choice(m, choices)
else:
return ans
def user_appends():
"""User adds a new line"""
user_text.append(user_input())
def user_reads(*args, **kwargs):
"""Print a set of lines for the user
Selects text via `user_text[slice(*args)]`
Use 'print_init' in kwargs to choose how
many lines are printed out before user must
scroll by pressing enter, or quit with 'q'."""
print_init = kwargs.get('print_init', 4)
sliced = user_text[slice(*args)]
if not isinstance(sliced, list):
sliced = [sliced]
for i, l in enumerate(sliced):
if i < print_init:
print(l)
sys.stdout.flush()
elif user_choice(l, ['', Quit]) == Quit:
break
def user_changes(i=None, save=False):
"""User changes a preexisting line"""
attempt = True
while i is None and attempt:
# get the line the user wants to change
i_text = raw_input("Line to be changed: ")
try:
# make user input an index
i = int(i_text)
except:
# check if they want to try again
c = user_choice("Bad input - '%s' is not an "
"integer. Try again? " % i_text, (Yes, No))
attempt = (c == Yes)
if attempt:
# user gave a valid integer for indexing
try:
user_reads(i-1)
overwrite_user_text(i-1, user_input(), save)
except Exception as e:
print("ERROR: %s" % e)
if user_choice("Try again? ", (Yes, No)):
user_changes(i, save)
# stores whatever text is already on
# file to `user_text` before use
read_from_cache()