I'm trying to make do with Python's lack of a switch statement and make code more efficient by using a dictionary, but I can't quite get what I'm looking for. Here's a simplified version of the code I'm working with
def zero():
print("Yo")
def one():
print("Hey")
options = {
0: zero,
1: one,
}
while True:
response = int(input("Number: "))
try:
options.get(response)()
except TypeError:
print("Not a valid response")
and what I would like to see is some way to break the loop such as 2: break that exits the loop. Currently I'm using sys.exit(0), but was wondering if it was possible to use the break keyword
You could define a LoopBreak exception, raise that in a two function, and catch it in the loop to break:
class LoopBreak(Exception):
pass
def zero():
print("Yo")
def one():
print("Hey")
def two():
raise LoopBreak
options = {
0: zero,
1: one,
2: two
}
while True:
response = int(input("Number: "))
try:
options.get(response)()
except TypeError:
print("Not a valid response")
except LoopBreak:
break
As a point of interest, this is similar to the pattern used natively by Python to stop generators; they raise a StopIteration exception when they run out of values to yield.
EDIT: As #mfripp correctly notes below, this will mask any TypeErrors that are raised during execution of zero or one. I would change the main loop to this instead (so you don't have to rely on TypeError):
while True:
response = int(input("Number: "))
action = options.get(response)
if action is None:
print("Not a valid response")
continue
try:
action()
except LoopBreak:
break
There are a few ways you could do this.
Here's a more robust version of #BingsF's clever answer (this one won't mask exceptions raised within the selected function):
class LoopBreak(Exception):
pass
def zero():
print("Yo")
def one():
print("Hey")
def two():
raise LoopBreak
options = {
0: zero,
1: one,
2: two
}
while True:
try:
response = int(input("Number: "))
action = options[response]
except (ValueError, KeyError):
print("Not a valid response")
continue
try:
action()
except LoopBreak:
break
Or you could specify a special flag in your dictionary that will force a break:
def zero():
print("Yo")
def one():
print("Hey")
options = {
0: zero,
1: one,
2: False
}
while True:
try:
response = int(input("Number: "))
action = options[response]
except (ValueError, KeyError):
print("Not a valid response")
continue
if action is False:
break
else:
action()
Or use a special return value to force a break:
def zero():
print("Yo")
# returns None by default
def one():
print("Hey")
# returns None by default
def two():
return False
options = {
0: zero,
1: one,
2: two
}
while True:
try:
response = int(input("Number: "))
action = options[response]
except (ValueError, KeyError):
print("Not a valid response")
continue
if action() is False:
break
The following code might be more "Pythonic". It could be infinitesimally slower than the approaches above, because it has to check all the if statements instead of looking up the function in a dictionary via a hash. But it may be easier to read and maintain.
while True:
try:
response = int(input("Number: "))
except ValueError:
response = -1 # error flag
if response == 0:
print("Yo")
elif response == 1:
print("Hey")
elif response == 2:
break
else:
print("Not a valid response")
This is all you need:
while True:
response = int(input("Number: "))
if response not in options:
print("Not a valid response")
else if response == 2:
break
else:
options[response]() # invoke the corresponding function
Incidentally, storing functions in a dictionary and having to invoke them like this isn't exactly Pythonic. It's a lot nicer to simply explicitly enumerate the behaviour you need with successive ifs.
Related
I have a bunch of functions that do x y and z, these functions get executed after an exception has occured. so my problem is that instead of writing the functions one by one i want to create a list of functions and a write a function to iterate through those functions in the list.
If you could show me an example that would be of great help.
here is what i have:
def call_va_funcs(self, stop):
self.disableZüruckVA()
while True:
try:
correct = urllib.request.urlopen("http://192.168.167.12", timeout=10).getcode()
print(correct, "192.168.100.2 is reachable: FAIL")
if correct == 200:
self.enableZüruckVA()
self.exitflag == True
break
except Exception:
print('Preflash')
if self.exitflag == True:
self.enableZüruckVA()
break
self.ping_all_va()
time.sleep(1)
if self.exitflag == True:
self.enableZüruckVA()
break
self.run_rxtx()
time.sleep(1)
if self.exitflag == True:
self.enableZüruckVA()
break
self.call_netzteil()
time.sleep(1)
if self.exitflag == True:
self.enableZüruckVA()
break
As you can see here i am repeating the same process over and over again but with a different function. Can anyone help me figure out how to have a list and then just iterate through the bunch of functions.
Thank you
Let's say you have functionA(), functionB() and functionC().
You could put the names of the functions in a list like this:
f_list = [functionA, functionB, functionC]
You can then call these functions one after another:
while True:
# other code here...
try:
#... other code
except:
# ...other code
for func in f_list:
func()
time.sleep(1)
if self.exitflag == True:
self.enableZüruckVA()
break
you could try smth like this
my_methods = []
my_methods.append('ping_all_va')
my_methods.append('run_rxtx')
my_methods.append('next_func')
for cmd in my_methods:
method = getattr(self, cmd)
method()
time.sleep(1)
if self.exitflag == True:
self.enableZüruckVA()
break
The code works fine except for the exceptions i.e., when I input something like cat/dog or 1/0 or for the case 3/2, instead of re-prompting, the terminal just goes into blank infinite mode that I have to manually stop with cmd+c. Please help identify what I'm doing wrong. Thanks!
def main():
fuel=input("Fuel: ")
c=convert(fuel)
print(f"{convert(fuel)}%")
gauge(c)
def convert(fraction):
while True:
try:
X,Y=fraction.split("/")
x=int(X)
y=int(Y)
f=x/y
z=int((x/y)*100)
if f<=1:
return z
except (ValueError, ZeroDivisionError):
pass
def gauge(percentage):
if percentage<=1:
print("E")
elif percentage>=99:
print("F")
else:
print("Z%")
if __name__ == "__main__":
main()
Well, you never re-ask the question. To solve it, move the input("Fuel: ") call inside the while loop:
def main():
c = convert()
print(f"{c}%")
gauge(c)
def convert():
while True:
fraction = input("Fuel:")
try: # Rest can be the same
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}")
lst = []
while True:
try:
arr = int(input("Enter number of elements: "))
if arr == "Quit":
break
except ValueError:
print("Invalid Input")
continue
else:
break
while True:
try:
for i in range(0, arr):
ele = int(input("Enter the elements:"))
lst.append(ele)
print(lst)
except ValueError:
print("Invalid Input")
continue
else:
break
How can I create a condition to exit the program at any point in the loop by specifically entering the conditioned term?
Like when it asks me to enter an element, but i want to break the program right at that point by entering
"Quit".
How to do that in this code?
(Learner)
Use sys.exit() to exit the program completely. Use break to exit the specific loop you're in (which you've used).
Other notes:
You're doing arr = int(input(...)) but you want to accept the input of "Quit" which is a string. So if the user enters "Quit", it raise ValueError and the loop continues. So check for "Quit" first and if it's not quit, then convert to int with the try-block
Same applies for the 2nd loop where you ask the user for the elements in the list
Btw, your second loop's while True loop should be inside the for-loop which gets each element.
lst = []
while True:
arr = input("Enter number of elements: ")
if arr == "Quit":
sys.exit() # will exit completely
try:
arr = int(arr) # check for int in the try-block, error raised here, if not int
break # can put break here instead of in else, any is okay
except ValueError:
print("Invalid Input")
# continue not needed here, since it loops infinitely by default
for i in range(0, arr):
while True:
ele = input("Enter the elements:")
if ele == "Quit":
sys.exit() # will exit completely
try:
ele = int(ele) # check for int in the try-block, error raised here, if not int
lst.append(ele)
break # breaks out of `while` loop, not `for` loop; good
except ValueError:
print("Invalid Input")
# continue not needed here, since it loops infinitely by default
print(lst) # print the full list after all the inputs
The pattern for your input is repeated:
Ask user for input
if input is "Quit", then exit completely
otherwise, convert the input to an int
So this can put into a function, which you call in both places:
def int_or_quit(msg):
"""`msg` is the message you want to show at input"""
while True:
item = input(msg)
if item == "Quit":
sys.quit() # will exit completely
try:
return int(item) # try converting to int and return
except ValueError:
print("Invalid Input")
# repeats by default
# use the function above in your two code blocks, which are now simplified:
lst = []
arr = int_or_quit("Enter number of elements: ")
# program exits before if user "Quit", next part won't execute
for i in range(0, arr):
ele = int_or_quit("Enter an element:")
lst.append(ele)
print(lst)
I can see that there're 2 blocks in your code snippet. I'd suggest organising them in distinct functions, where function name infers a function goal (instead of comments):
def get_list_size():...
def fill_list():...
Next thing to do is deciding about function's return values:
from typing import List, Optional
def get_list_size() -> Optional[int]:
"""
:returns: number of desired elements of list or None, if "Quit" was pressed
:raises: ValueError exception, if non integer value was pressed
"""
def fill_list(lst_size: int) -> List[int]:
"""
returns: filled list
throws: ValueError exception, if non integer value was pressed
"""
Here you can see, how Optional type hint is used to add condition logic into the program.
Lats thing to do is to fill your functions with lightweight code:
def get_list_size() -> Optional[int]:
"""
:returns: number of desired elements of list or None, if "Quit" was pressed
:raises: ValueError exception, if non integer value was pressed
"""
while True:
try:
input_value = input("Enter number of elements: ")
# Pay attention: you have to check string input_value, not int(input_value).
# Checking int(input_value) would raise exception before 'Quit' validation.
if input_value == "Quit":
return None
return int(input_value)
# Could be rewritten as trinary if
# return None if input_value == "Quit" else int(input_value)
except ValueError:
print("Invalid Input")
# continue <- Not needed here
def fill_list(lst_size: int) -> List[int]:
"""
returns: filled list
throws: ValueError exception, if non integer value was pressed
"""
lst = []
while len(lst) < lst_size:
try:
ele = int(input("Enter the elements:"))
lst.append(ele)
print(lst)
except ValueError:
print("Invalid Input")
return lst
Pay attention: in fill_list function you have to try/except each element, so while loop was moved outside.
Last thing to do is running these functions:
desired_list = []
desired_list_size = get_list_size()
if desired_list_size is not None:
desired_list = fill_list(desired_list_size)
def one():
message = ""
while message != "Quit":
message = input("Type in Order > ")
if message == "Run":
print("Run some Code")
message = input("What do u want to run ? > ")
if message == "Exel":
print("Runing Exel")
else:
pass
else:
pass
while True:
one()
Here you can pass in funktions that will run when you call it by its Name. If u write Quit it will start over again. It starts over again if you write something wrong. U could do an FailExeption for that if u want to.
Is there a command such as break and continue which could repeat recent iteration?
For example, when exception is thrown.
for i in range(0,500):
try:
conn = getConnection(url+str(i))
doSomething(conn)
except:
repeat
Let's have an iteration where i variable's value is 6. During this iteration some connection error occurred. I want to repeat this iteration.
Is there a command which can do that?
Of course I can do this:
i=0
while i!=500:
try:
conn = getConnection(url+str(i))
doSomething(conn)
i+=1
except:
pass
No, there is no command to "rewind" a for-loop in Python.
You could use a while True: loop inside the for-loop:
for i in range(500):
while True:
try:
conn = getConnection(url+str(i))
doSomething(conn)
except Exception: # Replace Exception with something more specific.
continue
else:
break
or without the else::
for i in range(500):
while True:
try:
conn = getConnection(url+str(i))
doSomething(conn)
break
except Exception: # Replace Exception with something more specific.
continue
But I personally think that your proposed solution is better because it avoids an indentation level.
for i in range(500):
while True
try:
conn = getConnection(url+str(i))
break
except Exception: # still allows to quit with KeyboardInterrupt
continue
do_your_stuff()
This looks bit risky, however, you should at least enable some logging inside a while block.
If you expect to use it in more places, you might write a simple decorator:
def keep_trying(fn, *args, **kwargs):
def inner(*args, **kwargs):
while True:
try:
return fn(*args, **kwargs)
except Exception:
continue
return inner
# later you can use it simple like this:
for i in range(500):
conn = keep_trying(getConnection)(url+str(i))
You can use generators :
def process_connections(n_connections, url, max_tries=50):
i = 0
try_count = 0
while i < n_connections:
try:
conn = getConnection(url+str(i))
yield conn
except:
try_count += 1
if try_count > max_tries:
raise Exception("Unable to connect after %s tries" % max_tries)
else:
i += 1 # increments only if no exception
And you perform your operations :
for conn in process_connections(500, url):
do_something(conn)
You can use nested for loops to put a cap on the number of times you retry the operation. This is bascially the sam as #PierreAlex's generator answer but without the extra function definition.
for i in range(500):
for retry in range(10):
try:
conn = getConnection(url+str(i))
doSomething(conn)
except Exception: # Replace Exception with something more specific.
time.sleep(1)
else:
print "iteration", i, "failed"
Why not just use an if statement?
n=6
i=0
while i!=500:
failed = False;
try:
conn = getConnection(url+str(i))
doSomething(conn)
i+=1
except:
#handle error
failed = True;
#try again if n-th case failed first time
if(i == n and failed):
try:
conn = getConnection(url+str(i))
doSomething(conn)
except:
#handle error
Here is one. You would need to add a logging or alert system to let you know that something is stuck:
state = "" #state of the loop
# If there is no error continue. If there is error, remain in loop
while True:
if state != "error":
try:
1/0 # command
break # no error so break out of loop
except:
state = "error" #declare error so maintain loop
continue
elif state == "error": # maintain loop
continue