why does this python while loop not work in the program? - python

I'm trying to run the program in the following article:
https://blockgeeks.com/guides/python-blockchain-2/
I've copied all of the code into my Spyder IDE. When i run it there's a while loop which starts up asking the user to choose a number from the list of options it prints.
After selecting a number the program should perform the requested action. When i select it though it just loops back to the start of the while loop.
It appears to be ignoring the rest of the code in the while loop (the if statement part).
Confusingly if i take the parts of the code from the program which are used in the while loop and run them separately they work i.e if i run the below code and select the number 1 for my choice it will run the code in the if statement.
Why would the if statement run here but not in the main program?
#function 1:
def get_user_choice():
user_input = input("enter a number: ")
return user_input
#function 2:
def get_transaction_value():
tx_recipient = input('Enter the recipient of the transaction: ')
tx_amount = float(input('Enter your transaction amount '))
return tx_recipient, tx_amount
while True:
print("Choose an option")
print('Choose 1 for adding a new transaction')
print('Choose 2 for mining a new block')
print('Choose 3 for printing the blockchain')
print('Choose anything else if you want to quit')
user_choice = get_user_choice()
if user_choice == '1':
tx_data = get_transaction_value()
print(tx_data)
Update:
Sorry i realise i may not have been very clear what the problem is.
The above code is part of the code from the entire program and runs as expected in isolation from the main program.
The below code is the entire program from the article in the link. It includes all of the code in the program. If i run this main program the while loop doesn't use the if statement. It appears to just be breaking straight out of the loop after i select 1, 2 or 3 (any other number should break out of the loop anyway).
Here's a link for a screen shot showing what the console looks like after i have selected the number 1 for the option.
https://ibb.co/RNy2r0m
# Section 1
import hashlib
import json
reward = 10.0
genesis_block = {
'previous_hash': '',
'index': 0,
'transaction': [],
'nonce': 23
}
blockchain = [genesis_block]
open_transactions = []
owner = 'Blockgeeks'
def hash_block(block):
return hashlib.sha256(json.dumps(block).encode()).hexdigest()
# Section 2
def valid_proof(transactions, last_hash, nonce):
guess = (str(transactions) + str(last_hash) + str(nonce)).encode()
guess_hash = hashlib.sha256(guess).hexdigest()
print(guess_hash)
return guess_hash[0:2] == '00'
def pow():
last_block = blockchain[-1]
last_hash = hash_block(last_block)
nonce = 0
while not valid_proof(open_transactions, last_hash, nonce):
nonce += 1
return nonce
# Section 3
def get_last_value():
""" extracting the last element of the blockchain list """
return(blockchain[-1])
def add_value(recipient, sender=owner, amount=1.0):
transaction = {'sender': sender,
'recipient': recipient,
'amount': amount}
open_transactions.append(transaction)
# Section 4
def mine_block():
last_block = blockchain[-1]
hashed_block = hash_block(last_block)
nonce = pow()
reward_transaction = {
'sender': 'MINING',
'recipient': owner,
'amount': reward
}
open_transactions.append(reward_transaction)
block = {
'previous_hash': hashed_block,
'index': len(blockchain),
'transaction': open_transactions,
'nonce': nonce
}
blockchain.append(block)
# Section 5
def get_transaction_value():
tx_recipient = input('Enter the recipient of the transaction: ')
tx_amount = float(input('Enter your transaction amount '))
return tx_recipient, tx_amount
def get_user_choice():
user_input = input("Please give your choice here: ")
return user_input
# Section 6
def print_block():
for block in blockchain:
print("Here is your block")
print(block)
# Section 7
while True:
print("Choose an option")
print('Choose 1 for adding a new transaction')
print('Choose 2 for mining a new block')
print('Choose 3 for printing the blockchain')
print('Choose anything else if you want to quit')
user_choice = get_user_choice()
if user_choice == 1:
tx_data = get_transaction_value()
recipient, amount = tx_data
add_value(recipient, amount=amount)
print(open_transactions)
elif user_choice == 2:
mine_block()
elif user_choice == 3:
print_block()
else:
break
[1]: https://i.stack.imgur.com/FIrn7.png

When comparing values, Python takes a stronger route regarding data types than some other languages. That means no string in Python will equal a number.
Or in other terms "1" == 1 will be False.
That means you have to consider that in Python 3 you will receive a string from input() (not necessarily so in Python 2).
You can either compare this directly to another string:
user_choice = input()
if user_choice == "1":
print("You chose item 1")
Or you can convert it into a number first and compare it to a number:
user_choice = int(input())
if user_choice == 1:
print("You chose item 1")
Note that in the former case it might not be robust if the user enters extra spaces and in the latter case it will fail very loudly with an exception if the user doesn't enter an integer (or even nothing at all).
Both ways can be handled with extra code if necessary. In the former case, you can strip whitespace with user_input = input().strip() and in the latter case you can catch the exception with a try ... except ... block.

You have only handled the case for user_choice == '1'. If you enter anything other than 1, the program will return control to the beginning of the while loop.
I'll suggest you use a debugger to see what user_choice is before the if condition. If not, just use prints.
print("user_choice: {}, type: {}".format(user_choice, type(user_choice))

Related

How can I unit test a function that is called by another function

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}")

Score function for a flashcard game in python

I am trying to add a very simple score function to an also very simple flashcard game and I can't make the game remember the value of the variable containing the score (it always resets it 0). The score is obviously relying on the honesty of the user (and that's fine) that has to press "Y" when guessing the word.
from random import *
def add_score():
pos_score = 0
score = input("Press Y if you got the correct word or N if you got it wrong!" )
if score == 'Y':
pos_score += 1
print(pos_score)
def show_flashcard():
""" Show the user a random key and ask them
to define it. Show the definition
when the user presses return.
"""
random_key = choice(list(glossary))
print('Define: ', random_key)
input('Press return to see the definition')
print(glossary[random_key])
def add_flashcard():
""" This function allows the user to add a new
word and related value to the glossary. It will
be activated when pressing the "a" button.
"""
key = input("Enter the new word: ")
value = input("Enter the definition: ")
glossary[key] = value
print("New entry added to glossary.")
# Set up the glossary
glossary = {'word1':'definition1',
'word2':'definition2',
'word3':'definition3'}
# The interactive loop
exit = False
while not exit:
user_input = input('Enter s to show a flashcard, a to add a new card. or q to quit: ')
if user_input == 'q':
exit = True
elif user_input == 's':
show_flashcard()
add_score()
elif user_input == 'a':
add_flashcard()
else:
print('You need to enter either q, a or s.')
Some notes:
I am aware that right now only the positive score is implemented in the code, but I figured it would be better to proceed step by step and have that working first.
Problem
In your def add_score(), you initialise the variable to 0 every time. Also, it is a local variable, which means you can only reference it from within your function add_score(). This means that every time you exit that function, that variable is completely deleted.
Solution
You need to make that a global variable, that is, initialise it to 0 at the start of the game, and outside your function. Then inside your add_score you simply reference to the global variable and increase it without initialising it every time:
from random import *
def add_score():
score = input("Press Y if you got the correct word or N if you got it wrong!" )
if score == 'Y':
global pos_score
pos_score += 1
print(pos_score)
# Set up the glossary
glossary = {'word1':'definition1',
'word2':'definition2',
'word3':'definition3'}
# The interactive loop
pos_score = 0 #NOTE you initialise it here as a global variable
exit = False
while not exit:
user_input = input('Enter s to show a flashcard, a to add a new card. or q to quit: ')
if user_input == 'q':
exit = True
elif user_input == 's':
show_flashcard()
add_score()
elif user_input == 'a':
add_flashcard()
else:
print('You need to enter either q, a or s.')
Note I skipped the irrelevant functions. However, usually changing the scope of variables like this is considered bad practice. What you should do is either have a class -- a bit overly complicated for this example -- or return a value to add from your add_score and add that value in the main loop. This would be the code:
from random import *
def add_score():
score = input("Press Y if you got the correct word or N if you got it wrong!" )
if score == 'Y':
#global pos_score
#pos_score += 1
#print(pos_score)
return 1
return 0
def show_flashcard():
""" Show the user a random key and ask them
to define it. Show the definition
when the user presses return.
"""
random_key = choice(list(glossary))
print('Define: ', random_key)
input('Press return to see the definition')
print(glossary[random_key])
def add_flashcard():
""" This function allows the user to add a new
word and related value to the glossary. It will
be activated when pressing the "a" button.
"""
key = input("Enter the new word: ")
value = input("Enter the definition: ")
glossary[key] = value
print("New entry added to glossary.")
# Set up the glossary
glossary = {'word1':'definition1',
'word2':'definition2',
'word3':'definition3'}
# The interactive loop
pos_score = 0 #NOTE you initialise it here as a global variable
exit = False
while not exit:
user_input = input('Enter s to show a flashcard, a to add a new card. or q to quit: ')
if user_input == 'q':
exit = True
elif user_input == 's':
show_flashcard()
pos_score += add_score()
print(pos_score)
elif user_input == 'a':
add_flashcard()
else:
print('You need to enter either q, a or s.')

Bank ATM Program login

I want to make this program that acts as a bank, how do I make sure the correct ID number must be entered with the correct pin and have it depending on the id you entered print hello then their name and prompt how much money they have in the bank.
attempts = 0
store_id = [1057, 2736, 4659, 5691, 1234, 4321]
store_name = ["Jeremy Clarkson", "Suzanne Perry", "Vicki Butler-Henderson", "Jason Plato"]
store_balance = [172.16, 15.62, 23.91, 62.17, 131.90, 231.58]
store_pin = [1057, 2736, 4659, 5691]
start = int(input("Are you a member of the Northern Frock Bank?\n1. Yes\n2. No\n"))
if start == 1:
idguess = ""
pinguess = ""
while (idguess not in store_id) or (pinguess not in store_pin):
idguess = int(input("ID Number: "))
pinguess = int(input("PIN Number: "))
if (idguess not in store_id) or (pinguess not in store_pin):
print("Invalid Login")
attempts = attempts + 1
if attempts == 3:
print("This ATM has been blocked for too many failed attempts.")
break
elif start == 2:
name = str(input("What is your full name?: "))
pin = str(input("Please choose a 4 digit pin number for your bank account: "))
digits = len(pin)
balance = 100
while digits != 4:
print("That Pin is Invalid")
pin = str(input("Please choose a 4 digit pin number for your bank account: "))
digits = len(pin)
store_name.append(name)
store_pin.append(pin)
I'm very impressed by how much you've elaborated on your program. Here's how I would view your solution.
So to create a login simulation, I would instead use a dictionary. That way you can assign an ID to a PIN. For example:
credentials = {
"403703": "121",
"3900": "333",
"39022": "900"
}
Where your ID is on the left side of the colon and the PIN is on the right. You would also have to assign the ID to a name that belongs to that ID using, you guessed it, a dictionary!
bankIDs = {
"403703": "Anna",
"3900": "Jacob",
"39022": "Kendrick"
}
Now that you've done that, you can create your virtual login system using if/else control flow. I've made my code like this:
attempts = 0
try:
while attempts < 3:
id_num = raw_input("Enter your ID: ")
PIN = raw_input("Password: ")
if (id_num in credentials) and (PIN == credentials[id_num]):
print "login success."
login(id_num)
else:
print "Login fail. try again."
attempts += 1
if attempts == 3:
print "You have reached the maximum amount of tries."
except KeyboardInterrupt:
print "Now closing. Goodbye!"
Note the try and except block is really optional. You could use the break operator like you did in your code if you wanted to, instead. I just like to put a little customization in there (Remember to break out of your program is CTRL-C).
Finally, Python has a way of making life easier for people by using functions. Notice I used one where I put login(id_num). Above this while loop you'll want to define your login so that you can display a greeting message for that particular person. Here's what I did:
def login(loginid):
print "Hello, %s!" % bankIDs[loginid]
Simple use of string formatting. And there you have it. The same can be done with displaying that person's balance. Just make the dictionary for it, then print the code in your login definition.
The rest of the code is good as it is. Just make sure you've indented properly your while-loop inside the elif on the bottom of your code, and your last 2 lines as well.
Hope I helped. Cheers!

Problems transferring information from one part of a function to another

While working on my program I have run into a problem where the information stored in Menu option 1 is not being transferred to Menu option 2. As you can see it is correctly stored when in menu one. When it returns to go to menu option 2 its like it never went to option 1.
update #1:
some suggestions I've had is to understand scope? from what I can tell the program is not passing the data along to its parent program even though I've typed out return in each of the definitions.
#Must be able to store at least 4 grades
#Each class can have up to 6 tests and 8 hw's
#Weighted 40%*testavg 40% hw average attendance is 20%
#User must be able to input a minimum grade warning
#after each test the your program must calculate the students average and issue warning if necessary
##Define the Modules##
import math
def menu (a): #2nd thing to happen
menuend = 'a'
while menuend not in 'e':
menuend = raw_input("Type anything other then 'e' to continue:\n")
print "What would you like to do ?"
menudo = 0
print "1 - Enter Courses\n2 - Select Course to Edit\n3 - Save File\n4 - Load File\n5 - Exit\n"
menudo = input("Enter Selection:")
if (menudo == 1):
menuchck = 0
menuchck = raw_input("\nYou have entered #1 (y/n)?:\n")
if menuchck in ["Yes","yes","y","Y"]:
x = m1()
else:
print "I'm sorry,",nam,",for the confusion, lets try again\n"
menu()
elif (menudo == 2):
menuchck1 = 0
menuchck1 = raw_input("\nYou have entered #2 (y/n)?:\n")
if menuchck1 in ["Yes","yes","y","Y"]:
x = m2()
else:
print "I'm sorry,",nam,",for the confusion, lets try again\n"
menu()
elif (menudo == 3):
print "Entered 3"
elif (menudo == 4):
print "Entered 4"
else:
print "Anything Else Entered"
def course(): #3rd thing to happen
b = {}
while True:
while True:
print "\n",name,", please enter your courses below ('e' to end):"
coursename = raw_input("Course Name:")
if (coursename == 'e'):
break
will = None
while will not in ('y','n'):
will = raw_input('Ok for this name : %s ? (y/n)' % coursename)
if will=='y':
b[coursename] = {}
print "\n",name,", current course load:\n",b
coursechck = None
while coursechck not in ('y','n'):
coursechck = raw_input("Are your courses correct (y/n)")
if coursechck =='y':
return b
else:
b = {}
print
##Menu Options##
def m1():
a = course()
return a
def m2():
print "Excellent",name,"lets see what courses your enrolled in\n"
print x
return x
###User Input Section###
name = raw_input("Enter Students Name:\n")
a = {}
menu(a)
raw_input("This is the end, my only friend the end")
In your if-elif blocks in the do==1 case, you write m1(), but for the last case, you write x=m1(). You should have the latter everywhere (by typing m1() you only run the function, but do not store the returned x anywhere).
By the way, you can avoid this if-elif confusion using if chck in ["Yes","yes","Y","y"]:

Python custom modules - error with example code

I am reading the book "Python Programming for the Absolute Beginner (3rd edition)". I am in the chapter introducing custom modules and I believe this may be an error in the coding in the book, because I have checked it 5 or 6 times and matched it exactly.
First we have a custom module games.py
class Player(object):
""" A player for a game. """
def __init__(self, name, score = 0):
self.name = name
self.score = score
def __str__(self):
rep = self.name + ":\t" + str(self.score)
return rep
def ask_yes_no(question):
""" Ask a yes or no question. """
response = None
while response not in ("y", "n"):
response = input(question).lower()
return response
def ask_number(question, low, high):
""" Ask for a number within a range """
response = None
while response not in range (low, high):
response = int(input(question))
return response
if __name__ == "__main__":
print("You ran this module directly (and did not 'import' it).")
input("\n\nPress the enter key to exit.")
And now the SimpleGame.py
import games, random
print("Welcome to the world's simplest game!\n")
again = None
while again != "n":
players = []
num = games.ask_number(question = "How many players? (2 - 5): ", low = 2, high = 5)
for i in range(num):
name = input("Player name: ")
score = random.randrange(100) + 1
player = games.Player(name, score)
players.append(player)
print("\nHere are the game results:")
for player in players:
print(player)
again = games.ask_yes_no("\nDo you want to play again? (y/n): ")
input("\n\nPress the enter key to exit.")
So this is exactly how the code appears in the book. When I run the program I get the error IndentationError at for i in range(num):. I expected this would happen so I changed it and removed 1 tab or 4 spaces in front of each line from for i in range(num) to again = games.ask_yes_no("\nDo you want to play again? (y/n): ").
After this the output is "Welcome to the world's simplest game!" and that's it.
I was wondering if someone could let me know why this is happening?
Also, the import games module, is recognized in Eclipse after I added the path to PYTHONPATH.
I actually have this book myself. And yes, it is a typo. Here is how to fix it:
# SimpleGame.py
import games, random
print("Welcome to the world's simplest game!\n")
again = None
while again != "n":
players = []
num = games.ask_number(question = "How many players? (2 - 5): ", low = 2, high = 5)
for i in range(num):
name = input("Player name: ")
score = random.randrange(100) + 1
player = games.Player(name, score)
players.append(player)
print("\nHere are the game results:")
for player in players:
print(player)
again = games.ask_yes_no("\nDo you want to play again? (y/n): ")
input("\n\nPress the enter key to exit.")
All I did was indent num 4 spaces and lined it up with the first for-loop.
You have an infinite loop here:
again = None
while again != "n":
players = []
If this is exactly the way it's printed in the book, the book does have an error.
You've got these two lines:
num = games.ask_number(question = "How many players? (2 - 5): ", low = 2, high = 5)
for i in range(num):
The second one is more indented than the first. That's only legal if the first one is a block-introducer like a for or while or if. Since it's not, this is an IndentationError. And that's exactly what Python is telling you.
(It's possible that you've copied things wrong. It's also possible that you're mixing tabs and spaces, so it actually looks right in your editor, but it looks wrong to Python. But if neither of those is true, the book is wrong.)
So, you attempted to fix it by dedenting everything from that for loop on.
But when you do that, only one line is still left under the while loop:
while again != "n":
players = []
There's nothing that can possibly change again to "n", so this will just spin forever, doing nothing, and not moving on to the rest of the program.
So, what you probably want to do is to indent the num = … line to the same level as the for i… line, so both of them (and all the stuff after) ends up inside the while loop.

Categories