Python 3 Jump Tables - python-3.x

I am trying to figure out how to create a basic jump table, so I can better understand different ways of creating menus in Python 3.5.6. Here is what I have so far:
def command():
selection = input("Please enter your selection: ")
return selection
def one():
print ("you have selected menu option one")
def two():
print ("you have selected menu option two")
def three():
print ("you have selected menu option three")
def runCommand(command):
jumpTable = 0
jumpTable[command]()
jumpTable = {}
jumpTable['1'] = one
jumpTable['2'] = two
jumpTable['3'] = three
def main():
command()
runCommand(command)
if __name__ == "__main__":
main()
As far as I understand, a jump table is simply a way of making a menu selection and calling a specific function associated with that numerical value, taken in by my "command" function. Within the jumpTable, you assign the function to call.
I am getting " File "omitted", line 16, in runCommandjumpTableone
TypeError: 'int' object is not subscriptable
All I want to do is have the user enter a number - 1, 2 or 3 and have that function run. when I get this basic functionality down, I will expand the menu to show the options and be more clear. I just need to get the darn thing to run!
Yes, I am aware of other ways to create menus (IF/ELIF/ELSE) I am just trying to nail this one down!
Thank you in advance!

You are quite close. The only issue is that you are trying to access the command before creating the jumpTable. And I am also not sure why you are setting the variable to 0 first (that's why you get the int is not subscriptible error). So, this is the right order:
def runCommand(command):
jumpTable = {}
jumpTable['1'] = one
jumpTable['2'] = two
jumpTable['3'] = three
jumpTable[command]()
By the way, if you are always creating the same jumpTable, you could create it once, outside the function and simply call jumpTable[command]() in your main function.
Another problem: you should store the value you get from the user and pass that to the next function like this:
cmd = command()
runCommand(cmd)
, or simply pipe the two functions together like this:
runCommand(command())

"""
Based on the original question, the following will.
Add a menu to a console application to manage activities.
Run a selected function.
Clear the output
Display the menu again or exit if done is selected
"""
import sys
from os import system
def display_menu(menu):
"""
Display a menu where the key identifies the name of a function.
:param menu: dictionary, key identifies a value which is a function name
:return:
"""
for k, function in menu.items():
print(k, function.__name__)
def one():
print("you have selected menu option one")
input("Press any Enter to return to menu.")
system('cls') # clears stdout
def two():
print("you have selected menu option two")
input("Press any Enter to return to menu.")
system('cls') # clears stdout
def three():
print("you have selected menu option three")
input("Press any Enter to return to menu.")
system('cls') # clears stdout
def done():
system('cls') # clears stdout
print("Goodbye")
sys.exit()
def main():
# Create a menu dictionary where the key is an integer number and the
# value is a function name.
functions_names = [one, two, three, done]
menu_items = dict(enumerate(functions_names, start=1))
while True:
display_menu(menu_items)
selection = int(
input("Please enter your selection number: ")) # Get function name
selected_value = menu_items[selection] # Gets the function name
selected_value() # add parentheses to call the function
if __name__ == "__main__":
main()

Related

How to create a menu system for a console, terminal application

I looked for "How to create a menu system for a console, terminal application". None of the proposed Similar questions fully answered my goal.
Add a menu to a console application to manage activities.
Run a selected function.
Clear the output
Display the menu again or exit if done is selected
Therefore, I am approaching this as a Q&A format. It’s OK to Ask and Answer Your Own Questions
Suggested better answers are welcomed.
"""
1. Add a menu to a console application to manage activities.
2. Run a selected function.
3. Clear the output
4. Display the menu again or exit if done is selected
"""
import sys
from os import system
def display_menu(menu):
"""
Display a menu where the key identifies the name of a function.
:param menu: dictionary, key identifies a value which is a function name
:return:
"""
for k, function in menu.items():
print(k, function.__name__)
def one():
print("you have selected menu option one") # Simulate function output.
input("Press Enter to Continue\n")
system('cls') # clears stdout
def two():
print("you have selected menu option two") # Simulate function output.
input("Press Enter to Continue\n")
system('cls') # clears stdout
def three():
print("you have selected menu option three") # Simulate function output.
input("Press Enter to Continue\n")
system('cls') # clears stdout
def done():
system('cls') # clears stdout
print("Goodbye")
sys.exit()
def main():
# Create a menu dictionary where the key is an integer number and the
# value is a function name.
functions_names = [one, two, three, done]
menu_items = dict(enumerate(functions_names, start=1))
while True:
display_menu(menu_items)
selection = int(
input("Please enter your selection number: ")) # Get function key
selected_value = menu_items[selection] # Gets the function name
selected_value() # add parentheses to call the function
if __name__ == "__main__":
main()
I am looking for a similar functionality and found this package: console_menu on PyPI. I haven't tried it out yet but apparently it should be simple to use. I copy their documentation example below.
# Import the necessary packages
from consolemenu import *
from consolemenu.items import *
# Create the menu
menu = ConsoleMenu("Title", "Subtitle")
# Create some items
# MenuItem is the base class for all items, it doesn't do anything when selected
menu_item = MenuItem("Menu Item")
# A FunctionItem runs a Python function when selected
function_item = FunctionItem("Call a Python function", input, ["Enter an input"])
# A CommandItem runs a console command
command_item = CommandItem("Run a console command", "touch hello.txt")
# A SelectionMenu constructs a menu from a list of strings
selection_menu = SelectionMenu(["item1", "item2", "item3"])
# A SubmenuItem lets you add a menu (the selection_menu above, for example)
# as a submenu of another menu
submenu_item = SubmenuItem("Submenu item", selection_menu, menu)
# Once we're done creating them, we just add the items to the menu
menu.append_item(menu_item)
menu.append_item(function_item)
menu.append_item(command_item)
menu.append_item(submenu_item)
# Finally, we call show to show the menu and allow the user to interact
menu.show()

NameError: free variable 'addcontact' referenced before assignment in enclosing scope

please be kind with my n00b question but it is driving me crazy and googling it didn't help much unfortunately.
I am trying to write a simple phonebook script using an empty dictionary as a learning exercise but as I am self taught there is no one else to turn to.
So here's what's happening,
I want to write a menu function that includes subfunctions for adding, deleting and editing {name:number} contacts
it is mostly working except for when I try to call the add function from within the edit
this is part of the menu code including the add routine
def menu():
selection = (int(input("""1:View phonebook
2:Add contact
3:Delete contact
4:Search for a contact and if necessary edit it
5:Save and Exit
What would you like to to?""")))
if selection == 1:
if len(phonebook)== 0:
print("Phonebook is empty, please add a contact first.")
loop = input("Press Enter to continue ...")
menu()
else:
print(phonebook)
loop = input("Press Enter to continue ...")
menu()
elif selection == 2:
def addcontact():
first = (str(input("First name: ")))
last = (str(input("Last name: ")))
num = (str(input("Number? ")))
phonebook.update({last + " " + first: num})
loop = input("Contact saved, press Enter to continue.")
menu()
(other stuff...)
menu()
and this is the update subroutine that's giving me trouble
elif selection == 4:
def search_and_edit():
search = (str(input("Please type the exact name of the contact you are looking for: ")))
if search in phonebook.keys():
print("Name: ", search, "Number: ", phonebook[search])
edit = (str(input("Do you wish to edit this contact? Y/N")))
if edit == "N" or edit == "n":
menu()
if edit == "Y" or edit == "y":
phonebook.pop(search)
addcontact()
else:
print("Contact not found.")
loop = input("Press Enter to continue.")
menu()
and this is the error message from PyCharm
File "C:\Users\Derek\PycharmProjects\pythonProject\phonebook\phonebook.py", line 78, in search_and_edit
addcontact()
NameError: free variable 'addcontact' referenced before assignment in enclosing scope
Process finished with exit code 1
what am I doing wrong here? the other callbacks work fine ("loop" callbacks to return to the main menu for example) but the addcontact() one fails with a variable error message even though it's a function
welcome aboard. The issue boils down to the addcontact being defined within the scope of a conditional (if...elif...else) and as a result not visible to search_and_edit which is defined in a different and mutually exclusive branch. So, when you chose 4, the program hadn't really entered 2 and the addcontact had not been "created" yet.
If you wish to make a function available at multiple places, then define it where it would in scope for all the callers.

Why callback function is bypassed

I am trying to develop a basic calculator and then introduce various functionalities.
I am new to Python but has been working with VB and c# for quite some time.
At present it has an Entry and three labels. I accept input from keyboard in Entry, and display on one label. Entry is hidden.
e.g. 52+56+36+45=189
User enters 52 and presses + sign
at this stage "+" is displayed on one label, 52 gets transferred to second label and Entry, first label becomes blank.
Till this stage it works properly. Now in step two when user is to enter 56 it allows allows all the keys without any validation and it stops printing output.
This means that callback function is bypassed.
Any help is welcome.
import tkinter
from tkinter import *
def callback(input):
if input.isdigit() or "." in input or input == "\b":
print(input)
return True
elif "+" in input :
print(input)
svLabelOpr.set("+")
svLabelDisp.set(svLabel.get())
svLabel.set("")
svTxt.set("")
txt.focus_set()
return False
else:
print(input)
return False
def oddblue(a,b,c):
svLabel.set(svTxt.get())
frm=Tk()
frm.geometry("250x250")
svTxt = StringVar()
svLabel = StringVar()
svLabelOpr = StringVar()
svLabelDisp = StringVar()
svTxt.trace('w',oddblue)
txt=Entry(frm, width=10, textvariable=svTxt)
txt.place(x=20, y=20)
reg=frm.register(callback)
txt.config(validate="key", validatecommand=(reg, '%S'))
lbl=Label(frm,anchor='e',width=15,relief=SUNKEN,textvariable=svLabel)
lbl.place(x=50,y=50)
lblOpr=Label(frm,width=3,relief=SUNKEN,textvariable=svLabelOpr)
lblOpr.place(x=180,y=50)
lblDisp=Label(frm,anchor='e', width=15,relief=SUNKEN,textvariable=svLabelDisp)
lblDisp.place(x=50,y=70)
txt.focus_set()
frm.mainloop()
Adding line:
txt.config(validate="key", validatecommand=(reg, '%S'))
here:
elif "+" in input :
print(input)
svLabelOpr.set("+")
svLabelDisp.set(svLabel.get())
svLabel.set("")
svTxt.set("")
txt.focus_set()
txt.config(validate="key", validatecommand=(reg, '%S')) # <---Here
return False
Maybe it deregisters due to some case. Configuring it again solves it.
Also, I'm not 100% sure, what you want to do. But, in case, if you want to add the values, you can replace this line:
svLabelDisp.set(svLabel.get())
with these:
try:
svLabelDisp.set(int(svLabelDisp.get())+int(svLabel.get()))
except ValueError:
svLabelDisp.set(svLabel.get())

How to ensure that the "bind" order is not skipped in tkinter?

I am trying to create a game using tkinter in which the players enter their names into an entry widget.
After entering their name, the user should press enter to call the function "player_names", which would ideally save the players name in a list, delete the text in the entry widget and then continue to the next loop (i.e player).
The script seems to be ignoring the bind and and moving straight to the line "self.name_entry.destroy()". How do I ensure that the script waits for the command before continuing?
def initialise_game(self, num_of_players):
self.players_list = []
for i in range(num_of_players):
player_num = i+1
self.name_label = tk.Label(self.bg_label, text='What is the name'
' of Player ' + str(player_num) + '?')
self.name_label.grid(row=0, padx=200, pady=120)
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
self.name_entry.destroy()
def player_names(self, event):
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
Entry doesn't work like input() it will not stop code and wait till you put text and press enter. GUI creates Entry and it executes code after Entry at once. You have bind to assign function which will be executed whey you press Enter and this function should get value from Entry, and replace widgets (or only text in Label). It also should remove Entry after last player so you have to count how many times function was executed (or how many players you already have - self.player_num)
I didn't test this code but it should works.
def initialise_game(self, num_of_players):
self.players_list = []
# remeber values in class variables, not local one
self.num_of_players = num_of_players
self.player_num = 0
# create only one Label - and change text in it
self.name_label = tk.Label(self.bg_label,
self.name_label.grid(row=0, padx=200, pady=120)
# create only one Entry and assign function `self.player_names`
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
# set text for first player
self.player_num += 1
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
def player_names(self, event):
# get player's name from Entry
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
# set text for next player or destroy Entry after last player
self.player_num += 1
if self.player_num <= self.num_of_players:
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
else:
self.name_label.destroy()
self.name_entry.destroy()
The same way it works in other GUIs (not only in tkinter) and other languages (not only in Python)
My idea of what are you trying to acomplish shuld be the same, to take an input from the player one by one until all player have added a name.
The ploblem with your code is the for, it needs to complite without an input from the player (tecnicly), insted i opted for the update mecanics that tkinter have, wen a value changes in a winget the winget will update itself and i bind to the button a change value from a increment.
If you want to reuse i , reference it a the start of the def the same wey
This example works, the input of initialise_game is the same as before, replacing the conde in the middle will work just fine, except if your code uses .grid() then replace ask_name.pack()
import tkinter as tk
root = tk.Tk()
root.bg_label = tk.Frame(root)
# Added code, top and bottom are for testing
#--------------------------------------------------------------------------------
root.players_list = [] # Global variabe witch can be accesd by all funcions
i = 1 # This global value is to keep a clear reference on the count of windows
def initialise_game(self, num_of_players):
ask_name = tk.Frame(self)
L = tk.Label(ask_name, text=('What is the name of Player ' + str(i) + '?'))
L.grid(row=0, padx=200, pady=120)
E = tk.Entry(ask_name)
def modify_entry(NULL):
root.players_list.append(E.get())
E.delete(0, 'end')
global i # Change the "search" of values to outside the funtion
i += 1
L.config(text=('What is the name of Player ' + str(i) + '?'))
if (i > num_of_players): # Close the Frame wen all were answered
root.players_list.append(E.get())
ask_name.destroy()
E.bind('<Return>', modify_entry)
E.grid(row=1, padx=200, pady=0)
ask_name.pack() # You can replace it with grin , this moves the Label + Entry were u want
#--------------------------------------------------------------------------------
initialise_game(root.bg_label, 3)
root.bg_label.pack()
root.mainloop()

tkinker optionmenu not showing chosen result

import tkinter
window = tkinter.Tk()
def abc(event):
ans=0
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=numberss[ans], command=efg)
ans=ans+1
def efg(event=None):
print('yee')
numbers = ['1','2', '3']
number=['4','5','6']
var = tkinter.StringVar(window)
var1 = tkinter.StringVar(window)
omenu = tkinter.OptionMenu(window, var, *numbers, command = abc)
omenu.grid(row=1)
omenu2 = tkinter.OptionMenu(window, var1, *number, command = efg)
omenu2.grid(row=2)
after you have entered the first option menu, it will update the second one. when you enter data into the second one, it runs the command, but doesn't show you what you entered. i do not want to include a button, and i know that the command works and not on the second
i found some code that changed the options of the second menu, however when i ran this, the command wouldn't work as it was changed to tkinter.setit (i would also like to know what is does. i do not currently understand it)
omenu2['menu'].add_command(label=numberss[ans], command=tkinter._setit(var1, number))
this has been taken from a larger piece of code, and has thrown the same error
You should set your StringVar(var1) new value.
def abc(event):
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=number, command=lambda val=number: efg(val))
def efg(val, event=None):
print('yee')
var1.set(val)
You are using for loop so you don't need ans(at least not in this code) since it iterates over items themselves.

Resources