"name is not defined" inside function inside condition - python

When I run the code below, and press the 'go' button, I get the error:
NameError: name 'be' is not defined
...on the following line in the countdown function:
if be >= 0:
Even if I add global be in the function countdown, I get the same error.
import tkinter as tk
def set1():
global be
if be1 is not '':
be = int(en.get())
def countdown():
global be
if be >= 0:
mins, secs = divmod(be, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
label['text'] = timer
root.after(1000, countdown)
be -= 1
root = tk.Tk()
label = tk.Label(root, text = '00:00', font = 'Helvetica 25')
label.pack()
en = tk.Entry(root)
en.pack()
be1 = en.get()
tk.Button(root, text = 'set', height = 3, width = 20, command = lambda: set1()).pack()
tk.Button(root, text = 'go', height = 3, width = 20, command = lambda: countdown()).pack()
root.mainloop()
Why doesn't global be resolve the problem?

The reason is clear from the error message: you need to define be as a global variable. Note that the global statement does not define the variable, it merely links the local references to that name with an existing global variable. But if it doesn't exist, there is nothing to match it with.
So you should define be as a global variable, like:
be = 0
There are a few other issues and things to improve:
be1 is read from the input only once, when the script starts, not when the user clicks a button. So its value will always remain the empty string. Instead, you should read the input into be1 when the "set" button is clicked, and so the variable be1 can just be a local variable in that set1 function, not global as it is now.
Don't use is not (or is) when comparing a variable with a string literal. So you should have:
if be1 != '':
It is better to already display the updated start-time when the user clicks the "set" button, even before the user clicks "go" -- otherwise they don't see the effect of clicking "set".
Since you already have code for displaying the timer in the countdown function, it would be good to avoid code duplication and make a new function just for doing the displaying.
So taking all that together you would get:
import tkinter as tk
be = 0 # define as global here
def display(): # new function to avoid code repetition
global be
mins, secs = divmod(be, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
label['text'] = timer
def set1():
global be
be1 = en.get() # local variable, reading fresh input value
if be1 != '': # don't use "is not" with a literal
be = int(be1)
display()
def countdown():
global be
if be >= 0:
display()
root.after(1000, countdown)
be -= 1
root = tk.Tk()
label = tk.Label(root, text = '00:00', font = 'Helvetica 25')
label.pack()
en = tk.Entry(root)
en.pack()
tk.Button(root, text = 'set', height = 3, width = 20, command = lambda: set1()).pack()
tk.Button(root, text = 'go', height = 3, width = 20, command = lambda: countdown()).pack()
root.mainloop()

Related

I don't know what's wrong with the program

variable "code" doesn't change (str) when i check and if i put checkbox() above codeget() there will be no checkbox
how to fix there is code
from tkinter import *
from tkinter import messagebox
from tkinter.ttk import *
#Window
window = Tk()
window.title("Register") #set window title
window.geometry('900x500')
code = 'None'
def codeget():
if (var1.get() == 1) and (var2.get() == 0):
code = 'Python'
elif (var1.get() == 0) and (var2.get() == 1):
code = 'C++'
elif (var1.get() == 0) and (var2.get() == 0):
code = 'None'
else:
code = 'Both'
var1 = IntVar()
var2 = IntVar()
c1 = Checkbutton(window, text='Python',variable=var1, onvalue=1, offvalue=0, command=codeget)
c1.place(x=650,y=120)
c2 = Checkbutton(window, text='C++',variable=var2, onvalue=1, offvalue=0, command=codeget)
c2.place(x=720,y=120)
def luu():
s=open("saveaccount", 'a+')
accountin4 = '\n' + 'Code: ' + code
s.write(accountin4)
message = Button(window,text = "Register", command = luu)
message.place(x = 500, y = 370)
mainloop()
if and else may not be related to my question so you can ignore it, even if I use = to assign " " to the code, the result will still be "None"
codeget() creates local variable code. You have to add global code inside this function to inform function that it has to assign value to global variable code instead of creating local variable code.
Minimal working code with small other changes.
import tkinter as tk # PEP8: `import *` is not preferred
from tkinter import messagebox
import tkinter.ttk as ttk
# --- functions --- # PEP8: all functions before main code
def get_code(): # PEP8: `lower_case_names` for functions and variables
global code # inform function to assign to global variable `code` instead of creating local variable `code`
if var1.get() == 0 and var2.get() == 0:
code = 'None'
elif var1.get() == 1 and var2.get() == 0:
code = 'Python'
elif var1.get() == 0 and var2.get() == 1:
code = 'C++'
else:
code = 'Both'
def register(): # PEP8: readable name
s = open("saveaccount", 'a+')
text = '\n' + 'Code: ' + code
s.write(text)
s.close() # better close file because system may keep data in buffer in RAM and write data when you close file
print(text)
# --- main ---
code = 'None' # it is global variable
window = tk.Tk()
var1 = tk.IntVar()
var2 = tk.IntVar()
c1 = tk.Checkbutton(window, text='Python', variable=var1, onvalue=1, offvalue=0, command=get_code)
c1.pack()
c2 = tk.Checkbutton(window, text='C++', variable=var2, onvalue=1, offvalue=0, command=get_code)
c2.pack()
message = tk.Button(window, text="Register", command=register)
message.pack()
window.mainloop()
PEP 8 -- Style Guide for Python Code
EDIT:
If you want to use more checkbuttons then you could use for-loop to create them, and StringVar to use string onvalue="Python" instead of integers 0/1
import tkinter as tk # PEP8: `import *` is not preferred
from tkinter import messagebox
import tkinter.ttk as ttk
# --- functions --- # PEP8: all functions before main code
def get_code():
global code # inform function to assign to global variable `code` instead of creating local variable `code`
selected = []
for v in string_vars:
if v.get():
selected.append(v.get())
if not selected:
code = 'None'
else:
code = ','.join(selected)
def register():
s = open("saveaccount", 'a+')
text = '\n' + 'Code: ' + code
print(text)
s.write(text)
s.close()
# --- main ---
code = 'None' # it is global variable
window = tk.Tk()
string_vars = []
for item in ['Python', 'C++', 'Java', 'PHP', 'Rust', 'Go']:
v = tk.StringVar(window)
c = tk.Checkbutton(window, text=item, variable=v, onvalue=item, offvalue='', command=get_code)
c.pack()
string_vars.append(v)
message = tk.Button(window, text="Register", command=register)
message.pack()
window.mainloop()

Why exec function in python adds '.!' at the beginning of the text?

I am currently working on a hangman game using tkinter in python.
When I click a button of the letter and it is in the word that we are guessing it should show the letter. But when I click the button this problem is popping up:
This example is only with one button. People say that this problem is because of the mainloop(), but i have no idea how to fix it.
from tkinter import *
from tkinter import messagebox
from generate_word import word
#DEFAULT VALUES
score = 0
count = 0
win_count = 2
WINDOW_BG = '#e5404e'
WINDOW_SIZE = '1200x870+300+80'
FONT = ('Arial', 40)
from tkinter import *
from tkinter import messagebox
from generate_word import word
#DEFAULT VALUES
score = 0
count = 0
win_count = 2
WINDOW_BG = '#e5404e'
WINDOW_SIZE = '1200x870+300+80'
FONT = ('Arial', 40)
#this is an example with only one button
buttons = [['b1','a',80,740]]
#Creating window and configurating it
window = Tk()
window.geometry(WINDOW_SIZE)
window.title('Hangman')
window.config(bg = WINDOW_BG)
#generates all of the labels for the word
def gen_labels_word():
label = Label(window, text = " ", bg = WINDOW_BG, font = FONT)
label.pack( padx = 40,pady = (500,100),side = LEFT)
label1 = Label(window, text = word[0], bg = WINDOW_BG, font = FONT)
label1.pack( padx = 41,pady = (500,100),side = LEFT)
x = 21
for var in range(1,len(word)):
exec('label{}=Label(window,text="_",bg=WINDOW_BG,font=FONT)'.format(var))
exec('label{}.pack(padx = {}, pady = (500,100), side=LEFT)'.format(var,x))
x += 1
exec('label{} = Label(window, text = "{}", bg = WINDOW_BG, font = FONT)'.format(len(word),word[-1]))
exec('label{}.pack( padx = {},pady = (500,100),side = LEFT)'.format(len(word), x+1))
# ---------------------------------------------------------------------------------------------------------------------------------------------------
gen_labels_word()
#----------------------------------------------------------------------------------------------------------------------------------------------------
#letters icons(images)
#hangman (images)
hangman = ['h0','h1','h2','h3','h4','h5','h6']
for var in hangman:
exec(f'{var}=PhotoImage(file="{var}.png")')
han = [['label0','h0'],['label1','h1'],['label2','h2'],['label3','h3'],['label4','h4'],['label5','h5'],['label6','h6']]
for p1 in han:
exec('{}=Label(window, bg = WINDOW_BG ,image={})'.format(p1[0],p1[1]))
exec('label0.place(x = 620,y = 0)')
for var in letters:
exec(f'{var}=PhotoImage(file="{var}.png")')
for var in buttons:
exec(f'{var[0]}=Button(window,bd=0,command=lambda: game_brain("{var[0]}","{var[1]}"),bg = WINDOW_BG,font=FONT,image={var[1]})')
exec('{}.place(x={},y={})'.format(var[0],var[2],var[3]))
def game_brain(button, letter):
global count,win_count,score
exec('{}.destroy()'.format(button))
if letter in word:
for i in range(1,len(word)):
if word[i] == letter:
win_count += 1
exec(f'label{i}.config(text="{letter}")')
if win_count == len(word):
score += 1
messagebox.showinfo('GOOD JOB, YOU WON!\n GOODBYE!')
window.destroy()
else:
count += 1
exec('label{}.destroy()'.format(count-1))
exec('label{}.place(x={},y={})'.format(count,620,0))
if count == 6:
messagebox.showinfo('GAME OVER','YOU LOST!\nGOODBYE!')
window.destroy()
def EXIT():
answer = messagebox.askyesno('ALERT','Do you want to exit the game?')
if answer == True:
window.destroy()
e1 = PhotoImage(file = 'exit.png')
ex = Button(window,bd = 0,command = EXIT,bg = WINDOW_BG,font = FONT,image = e1)
ex.place(x=1050,y=20)
#-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
window.mainloop()
Why exec function in python adds '.!' at the beginning of the text?
The exec function isn't doing that. Tkinter by default names all of its widgets with a leading exclamation point. When you print out a widget, it has to be converted to a string. For tkinter widgets, the result of str(some_widget) is the widget's name.
You can see this quite easily without exec:
import tkinter as tk
root = tk.Tk()
label = tk.Label(root)
print(label)
The above will print something like .!label. If you create a second label it will be .!label2, a third will be .!label3 and so on.
On an unrelated note, you shouldn't be using exec to create widgets. It makes the code very hard to understand and debug. If you want to create widgets in a loop, add them to a dictionary or list instead of dynamically creating variables with exec.
For example:
labels = {}
for var in range(1,len(word)):
label = Label(window,text="_",bg=WINDOW_BG,font=FONT)
label.pack(padx=500, pady=100)
labels[var] = label
With that, you can later reference the widgets as labels[1], labels[2], etc.
You should do the same thing with the images, or anything else that you create in a loop and want to keep track of.

My variable will not change in function

My variable is not changing and I know it's not changing because "1" is printed to the console.
I'm trying to make the label increment when i press the button. However when I press the button, the variable stays at 1.
What am I doing wrong?
I've looked online for an answer but I cannot really find one that I can understand.
num = 0
import tkinter
box = tkinter.Tk()
v = tkinter.StringVar()
labels = tkinter.Label(box, textvariable = v)
labels.pack()
def numberz(num,v):
num += 1
v.set(num)
print(num)
class MainWindow():
box.title("My Stupid Program")
buddon = tkinter.Button(box, text='PRESS ME', command = lambda:numberz(num,v))
buddon.pack()
box.mainloop()
num = 0
import tkinter
box = tkinter.Tk()
v = tkinter.StringVar()
labels = tkinter.Label(box, textvariable = v)
labels.pack()
def numberz(num,v):
num += 1
v.set(num)
print(num)
class MainWindow():
box.title("My Stupid Program")
buddon = tkinter.Button(box, text='PRESS ME', command = lambda:numberz(num,v))
buddon.pack()
box.mainloop()
You are changing the parameter num and not the global variable num
To change the global you need to specifically reference it. Notice how num is not passed in the lambda and now there is a global num in you function.
num = 0
import tkinter
box = tkinter.Tk()
v = tkinter.StringVar()
labels = tkinter.Label(box, textvariable = v)
labels.pack()
def numberz(v):
global num
num += 1
v.set(num)
print(num)
class MainWindow():
box.title("My Stupid Program")
buddon = tkinter.Button(box, text='PRESS ME', command = lambda:numberz(v))
buddon.pack()
box.mainloop()
In any case, using globals should be restricted to very specific cases and not be of general use.

Getting Pyphons Tkinter to update a label with a changing variable [duplicate]

This question already has answers here:
Making python/tkinter label widget update?
(5 answers)
Closed 7 years ago.
I have a python script which I have written for a Raspberry Pi project, the script reads a value from a micrometer every second, and stores this value as a TkInter StringVar (http://effbot.org/tkinterbook/variable.htm)
What I want to happen is that that string is displayed on the UI as a label, and updated on the UI when the value changes.
I chose to use a Tkinter stringvar instead of a standard string because I thought that Tkinter would then automatically update on the UI, but it doesn't seem to work.
Currently the script displays nothing for the label, and does not update when show sets virtual_reading.
From other threads I have read, the Tkinter method update_idletasks() should force the window to update, but I haven't been able to get this to work.
Can anyone give any guidance on where I'm going wrong?
This is my first go at Python so sorry if this is simple.
import datetime
import csv
from tkinter import *
from tkinter import messagebox
import time
import pigpio
import os
os.system("sudo pigpiod")
root = Tk()
winx = 480
winy = 320
CLOCK=21
DATA=20
g_level=0
g_reading=0
g_bits=0
pi = pigpio.pi()
virtual_reading = StringVar()
def go():
global cb1
global cb2
cb1 = pi.callback(DATA, pigpio.EITHER_EDGE, cbf)
cb2 = pi.callback(CLOCK, pigpio.EITHER_EDGE, cbf)
root.after(1000 ,go)
def show(bits, value):
inch = value & (1<<23)
minus = value & (1<<20)
value = value & 0xfffff
if inch:
reading = value / 2000.0
units = "in"
else:
reading = value / 100.0
units = "mm"
if minus:
sign = "-"
else:
sign = ""
global virtual_reading
virtual_reading = StringVar()
virtual_reading.set("{} {:.3f} {}".format(sign, reading, units))
print(virtual_reading.get())
cb2.cancel()
cb1.cancel()
def measure(event=None):
todays_date = datetime.date.today()
try:
get_tool_no = int(tool_no_entry.get())
if get_tool_no <= 0:
messagebox.showerror("Try Again","Please Enter A Number")
else:
with open("thickness records.csv", "a") as thicknessdb:
thicknessdbWriter = csv.writer(thicknessdb, dialect='excel', lineterminator='\r')
thicknessdbWriter.writerow([get_tool_no] + [todays_date] + [virtual_reading.get()])
thicknessdb.close()
except:
messagebox.showerror("Try Again","Please Enter A Number")
tool_no_entry.delete(0, END)
def cbf(g, l, t):
global g_level, g_reading, g_bits
if g == DATA:
if l == 0:
g_level = 1
else:
g_level = 0
elif g == CLOCK:
if l == pigpio.TIMEOUT:
if g_bits > 10:
show(g_bits, g_reading)
g_reading=0
g_bits=0
elif l == 0:
g_reading = g_reading | (g_level<<g_bits)
g_bits += 1
go()
record_button = Button(root,width = 30,
height = 8,
text='Measure',
fg='black',
bg="light grey", command = measure)
tool_no_entry = Entry(root)
reading_display = Label(root, font=("Helvetica", 22), text = virtual_reading.get())
reading_display.place(x = 50, y =80)
root.resizable(width=FALSE, height=FALSE)
root.geometry('%dx%d' % (winx,winy))
root.title("Micrometer Reader V1.0")
record_button.place(x = 340, y = 100, anchor = CENTER)
tool_no_entry.place(x = 120, y = 250, anchor=CENTER)
tool_no_entry.focus_set()
root.bind("<Return>", measure)
root.mainloop()
cb2.cancel()
cb1.cancel()
pi.stop()
You are misunderstanding how StringVars work. For one, you're recreating the StringVar every second, and only the original one is tied to the label. The one you create isn't associated with any widgets so it will never be visible.
The second problem is that you're associating the variable with the label incorrectly. You're doing this:
reading_display = Label(..., text = virtual_reading.get())
... when you should be doing it like this to get the auto-update feature:
reading_display = Label(..., textvariable = virtual_reading)
That being said, you don't need to use a StringVar at all. You can use it, but it's just an extra object you have to manage. You can directly set the displayed string with the configure method anytime you want
text = "{} {:.3f} {}".format(sign, reading, units)
reading_display.configure(text=text)
Note: you do not need to call update or update_idletasks

tkinter button double use

I'm new here and new in PY, I'm trying to write a simple GUI with Tkinter (py 2.7.10) where there are two buttons: the first one starts printing stuff and the second one quits.
I'd like the first button to change the text after the first click from "START" to "STOP" and of course to stop the loop, so I can pause instead of closing and reopen every time.
Also feel free to give any advice to improve it :)
I hope it's clear, here is the code.
import random, time
from Tkinter import *
START, STOP = "start", "stop"
class AppBase:
def __init__(self, root):
self.myRoot = root
self.frame1 = Frame(root)
self.frame1["background"] = "LightSteelBlue"
self.frame1.pack()
self.delay = Scale(self.frame1, from_=100, to=0)
self.delay.pack(side = LEFT, padx=5, pady=15)
self.label0 = Label(self.frame1, text="Notes", background="LightSteelBlue", foreground="darkblue")
self.label0.pack(padx=5, pady=15)
self.label1 = Label(self.frame1, text="NOTE", background="LightSteelBlue", foreground="SteelBlue")
self.label1.pack(padx=30, pady=10)
self.label2 = Label(self.frame1, text="STRING", background="LightSteelBlue", foreground="SteelBlue")
self.label2.pack(padx=30, pady=7)
self.label3 = Label(self.frame1, text="FINGER", background="LightSteelBlue", foreground="SteelBlue")
self.label3.pack(padx=30, pady=7)
self.puls1 = Button(self.frame1)
self.puls1.configure(text = "Start", background = "CadetBlue", borderwidth = 3, command = self.generate_notes)
self.puls1.pack(side = LEFT, padx=5, pady=15)
self.puls2 = Button(self.frame1)
self.puls2.configure(text = "Exit", background = "CadetBlue", borderwidth = 3)
self.puls2.pack(side = LEFT, padx=5, pady=15)
self.puls2.bind("<Button-1>", self.close_all)
self.notes_process=1
def generate_notes(self):
self.notes = ['DO','DO#','RE','RE#','MI','MI#','FA','FA#','SOL','SOL#','LA','LA#','SI','SI#']
self.strings = ['1^ corda','2^ corda','3^ corda','4^ corda','5^ corda','6^ corda']
self.fingers = ['Indice','Medio','Anulare','Mignolo']
self.note = random.randrange(0, len(self.notes))
self.string = random.randrange(0, len(self.strings))
self.finger = random.randrange(0, len(self.fingers))
self.timer=self.delay.get()
if self.timer == '':
self.timer = 500
elif int(self.timer) < 1:
self.timer = 500
else:
self.timer=int(self.delay.get())*100
self.label1["text"] = self.notes[self.note]
self.label2["text"] = self.strings[self.string]
self.label3["text"] = self.fingers[self.finger]
self.myRoot.after(self.timer, self.generate_notes)
def close_all(self, evento):
self.myRoot.destroy()
self.myRoot.quit()
def main():
master = Tk()
master.title("Notes")
appBase = AppBase(master)
master.mainloop()
main()
I found a solution, thanks to everybody for their help and of course if you want to keep talking about different and (sure) better way to do, you are more than welcome!
Here my solution:
step 1, add this to the button:
self.puls1.bind("<Button-1>", self.puls1Press1)
step 2, add a new variable:
self.active = True
step 3, create a new function:
def puls1Press1(self, evento):
if self.puls1["text"] == "Start":
self.puls1["text"] = "Stop"
self.active = True
else:
self.puls1["text"] = "Start"
self.active = False
step 4, modify the function that I want to stop:
def generate_notes(self):
if self.active == True:
[some code]
[some code]
else:
return
You need to save the return value of the after, so that you can use it when you cancel the loop using after_cancel(the_return_value_of_after).
def generate_notes(self):
if self.puls1['text'] == 'Start':
# Change button text to Stop, and call the original loop function
self.puls1['text'] = 'Stop'
self._generate_notes()
else:
# cancel timer
self.puls1['text'] = 'Start'
self.myRoot.after_cancel(self.timer_handle)
def _generate_notes(self):
...(old codes)..
self.timer_handle = self.myRoot.after(self.timer, self._generate_notes)
StringVar() from variable class can be used to update text as well as a looping condition. Example code:
ButtonText=StringVar()
ButtonText.set("START")
button1 = tkinter.Button(self.frame1, text=ButtonText, width=25, command= lambda: do_something(ButtonText))
Elsewhere where you are looping check for the value of the variable:
My_Loop():
if(ButtonText.get()=="STOP"):
break
else:
#some print statements
Now in function do_something(ButtonText), which will be called after each button press, change the string to "STOP" or vice versa :
do_something(ButtonText):
if(ButtonText.get()=="START): #Change text
ButtonText.set("STOP")
else:
ButtonText.set("START") #Change text and call function to loop again
My_Loop()
Hope this helps.

Categories