get rid of global variable in python3 - python-3.x

First of all I'm using python 3.3 & 3.2 on windows and Linux respectively.
I am starting to build an rpn calculator. It looks like cross-platform key listeners is a kind of holy grail for python. So far this seems to be doing the trick, but I've created other problems:
I cannot get away from the global variable for entries and using my
stack.
It looks like I have to build the program from inside
callback()
Here is a rough skeleton that shows my direction. Am I missing a way to pass information in and out of callback()
The goal was to build an RPN class before i found myself stuck inside callback().
import tkinter as tk
entry = ""
stack = list()
operators = {"+",
"-",
"*",
"/",
"^",
"sin",
"cos",
"tan"}
def operate(_op):
if _op == "+":
print("plus")
def callback(event):
global entry, stack
entry = entry + event.char
if event.keysym == 'Escape': # exit program
root.destroy()
elif event.keysym=='Return': # push string onto stack TODO
print(entry)
entry = ""
elif entry in operators:
operate(entry)
root = tk.Tk()
root.withdraw()
root.bind('<Key>', callback)
root.mainloop()

You have several options to do what you want to do.
1. Use a Class for your application
The canonical way of doing what you wish without resorting to a global variable is to place the application within a class, and pass a method as a callback (see print_contents) the following is straight from the docs:
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.entrythingy = Entry()
self.entrythingy.pack()
# here is the application variable
self.contents = StringVar()
# set it to some value
self.contents.set("this is a variable")
# tell the entry widget to watch this variable
self.entrythingy["textvariable"] = self.contents
# and here we get a callback when the user hits return.
# we will have the program print out the value of the
# application variable when the user hits return
self.entrythingy.bind('<Key-Return>',
self.print_contents)
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
2. Curry the callback over your state
You can also use Python's functional programming constructs to curry a function over a global variable and then pass the curried function as a callback.
import functools
global_var = {}
def callback(var, event):
pass
#...
root.bind('<Key>', functools.partial(callback, global_var))
Although this probably isn't what you want.
3. Use a global variable
Sometimes, a global variable is ok.
4. Re-architect for cleanliness and readability
However, you most definitely do not have to build your program inside the callback.
In fact, I would recommend that you create a suite of test with various valid and invalid input, and make a Calculator class or function that takes a string input of RPN commands and returns a value. This is easy to test without a tkinter interface, and will be far more robust.
Then, use your callback to build a string which you pass to your Calculator.
If you want incremental calculation (ie, you're building a simulator), then simply make your Calculator accept single tokens rather than entire equations, but the design remains similar. All the state is then encapsulated inside the Calculator rather than globally.

Related

Coding a general function to: Disable a tkinter widget with a checkbutton

I'm trying to write a function to disable a widget in a tkinter program depending on the value of a checkbutton. I want this function to be general: That is, I can pass it the widget and associated check variable and it will disable the widget (if the variable is checked the right way).
Here is an abstracted version of my code
import tkinter
class App:
def __init__(self,root):
widg = tkinter.Scale(root,from_=0,to=100)
checkvar = tkinter.IntVar()
checker = tkinter.Checkbutton(root,variable=checkvar,command=self.check(var,checkvar))
widg.grid()
checker.grid()
widg.configure(state=tkinter.DISABLED)
widg.configure(state=tkinter.NORMAL)
def check(self,widget,var):
if var.get()==1:
widget.configure(state=tkinter.DISABLED)
elif var.get()==0:
widget.configure(state=tkinter.NORMAL)
m = tkinter.Tk()
f=App(m)
It is intended to function such that clicking the checkbutton triggers the callback - check - with the parameters of the widget and the check variable. Then it will evaluate whether the widget should be on or off and change its state accordingly. There are no errors but the state doesn't change. What am I missing here?
Thanks
The command argument simply takes the uncalled function so passing arguments to it takes some workaround.
So it expects self.check rather than self.check() since the widget will call the function later.
I've found that using partial is a workaround for passing the arguments.
import tkinter
from functools import partial
class App:
def __init__(self,root):
widg = tkinter.Scale(root,from_=0,to=100)
checkvar = tkinter.IntVar()
checker = tkinter.Checkbutton(root,variable=checkvar,command=partial(self.check, widg, checkvar))
widg.grid()
checker.grid()
widg.configure(state=tkinter.DISABLED)
widg.configure(state=tkinter.NORMAL)
def check(self,widget,var):
if var.get()==1:
widget.configure(state=tkinter.DISABLED)
elif var.get()==0:
widget.configure(state=tkinter.NORMAL)
m = tkinter.Tk()
f=App(m)
m.mainloop()
Credit to my friend JB for helping me with this.
As user Axe319 suggested, the problem is just that tkinter doesn't expect to pass any parameters. It is possible to work around this by using a lambda function. The line that defines the checker variable, in my original post line 8, can be rewritten as such:
self.checker = tkinter.Checkbutton(root,variable=checkvar,command=lambda:self.check(self.widg,checkvar))
and it will work as intended.

How to use a variable inside of a function without declaring it as a global variable

I have a 2 part question (if that's not allowed, I really only need to first part answered)
I have the following sample code
import tkinter as tk
window = tk.Tk()
def countIncrease():
count +=1
t1.insert(tk.END,count)
count = 0
t1=tk.Text(window,height=3,width=30)
t1.grid(row=0,column=0,columnspan=3)
b1=tk.Button(window,text="+",height=3,width=10,command=countIncrease)
b1.grid(row=1,column=0)
window.mainloop()
and if I execute this code, I get the error UnboundLocalError: local variable 'count' referenced before assignment
I know that I could simply fix this by adding global count to the function
After I do that, when I press the button, the output is 1, and repeated presses produce 12, 123, 1234, 12345 and so on.
My first (and main) question is that I know it is bad practice to make variables global. What would be the proper way of making this work without making count a global variable?
My second question is how do I make the screen "refresh" so it is only showing the up to date variable, ie instead of 123 its just 3.
You should restructure your code to use a class and make count a class variable if you don't want to use a global variable. And to 'refresh' the screen/tkinter text, you need to delete the content before inserting new one.
Here is one way you can address the two issues:
import tkinter as tk
class app():
def __init__(self, parent):
self.count = 0
self.t1=tk.Text(parent, height=3,width=30)
self.t1.grid(row=0,column=0,columnspan=3)
self.b1=tk.Button(parent,text="+",height=3,width=10,command=self.countIncrease)
self.b1.grid(row=1,column=0)
def countIncrease(self):
self.count +=1
self.t1.delete('1.0', tk.END) #refresh/delete content of t1
self.t1.insert(tk.END,self.count)
window = tk.Tk()
app(window) # Create an instance of app
window.mainloop()

Passing a Function as a Parameter for a Class, eventually turning it into a method

Sorry if the title is confusing. I'm writing a minimalist game engine, and trying to define a class called "Area" where if the player enters the area, a function defined by the user happens. For example, one could create an instance
Area(location,function) that would fire function on the player when the player enters location (for the sake of simplicity, let it be a point or something).
Note: in pseudo-python
# in init.py
...
def function(player):
kill player
deathZone = Area(location,function)
--------------------------------------
# in player.update()
...
for area on screen:
if player in area:
Area.function(player)
The point of this is that the developer (aka me) can use any function they choose for the area. Is there anyway to do this, or should I try a better approach?
Sure, this kind of thing is certainly possible. In python, everything is an object, even a function. So you can pass around a function reference as a variable. For example try the following code:
import math
def rectangle(a, b):
return a*b
def circle(radius):
return math.pi * radius**2
class FunctionRunner(object):
def __init__(self):
self.userFunction = None
self.userParams = None
def setUserFunction(self, func, *params):
self.userFunction = func
self.userParams = params
def runFunction(self):
return self.userFunction(*self.userParams)
if __name__ == '__main__':
functionRunner = FunctionRunner()
functionRunner.setUserFunction(rectangle, 6, 7)
print(functionRunner.runFunction())
functionRunner.setUserFunction(circle, 42)
print(functionRunner.runFunction())
Here you have two functions that are defined for an area, and a class called FunctionRunner which can run any function with any number of input arguments. In the main program, notice that you need only pass the reference to the function name, and any input arguments needed to the setUserFunction method. This kind of thing will allow you to execute arbitrary code on the fly.
Alternatively, you could also replace a method on your class with a reference to another function (which is what you are asking), though this seems less safe to me. But it is certainly possible. For example you could have a class like this:
class FunctionRunner2(object):
def __init__(self):
pass
def setUserFunction(self, func):
self.theFunction = func
def theFunction(self, *params):
pass
And then do this:
if __name__ == '__main__':
functionRunner2 = FunctionRunner2()
functionRunner2.setUserFunction(rectangle)
print(functionRunner2.theFunction(6,7))
functionRunner2.setUserFunction(circle)
print(functionRunner2.theFunction(42))

Passing StringVar object from Entry to Label within functions in tkinter

Hi I've been struggling to get this to work, each time i change something I receive another error. I've been trying to create an entry box with a function and then get the variable from the entry box into a label, created by a button press. When I tried to do this often this error came up.
TypeError: get() missing 1 required positional argument: 'self'
I then put self in in the method brackets.
command = lambda: x.myFunc(self.my_variable.get(self))
Then another error, which I'm not sure how to sort out.
AttributeError: 'My_Class' object has no attribute '_tk'
Here's the full code, I'm new to classes and self, so any corrections are welcome.
from tkinter import *
import time
class My_Class:
def start(self):
self.root=Tk()
self.my_variable=StringVar
self.entry_box=Entry(self.root, textvariable=self.my_variable)
self.entry_box.pack()
self.button=Button(self.root,text="Pass variable now",
command=lambda:x.myFunc(self.my_variable.get(self)))
self.button.pack()
def myFunc(self,my_variable):
self.lab=Label(self.root,text=self.my_variable)
self.lab.pack()
x=My_Class()
x.start()
This is the correct way to create a StringVar object:
text = StringVar() # note additional ()
Can you explain me what x is in the following statement:
lambda: x.myFunc(self.my_variable.get(self))
x is not visible inside the class, because it's declared outside the class.
myFunc is not indented correctly: you should indent it like the __init__ method.
I really recommend you to watch some tutorials on OOP before proceeding. You are basically trying to guess how OOP works.
If you make myFunc A method if the class (which you might be trying to do; it's hard to know because your indentation is wrong), you don't have to pass anything to myFunc. That function has access to everything in the class, so it can get what it needs, when it needs it. That lets you eliminate the use of lambda, which helps reduce complexity.
Also, you normally don't need a StringVar at all, it's just one more thing to keep track of. However, if you really need the label and entry to show exactly the same data, have them share the same textvariable and the text is updated automatically without you having to call a function, or get the value from the widget, or set the value n the label.
Here's an example without using StringVar:
class My_Class:
def start(self):
...
self.entry_box = Entry(self.root)
self.button = Button(..., command = self.myFunc)
...
def myFunc(self):
s = self.entry_box.get()
self.lab = Label(..., text = s)
...

Entry Validation going wrong with tkinter

I am currently learning the tkinter basics and I'm building a small, super-simple program to test my knowledge on some of the most basic widgets.
I am having a problem with validation and an entry, possibly because of my lack of understanding in the matter... This poses three questions:
1 - How to do what was done here: https://stackoverflow.com/a/4140988/2828287 without the class part. Just doing it when the script runs.
2 - What are all those self. and .self doing there? Which ones are there because that is a class, and which ones are there because of the validating method itself??
3 - What's wrong in my code? based in this explanation >> http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
from tkinter import *
from tkinter import ttk
# function that should take the '%d' replacer and only validate if the user didn't delete
def isOkay(self, why):
if why == 0:
return False
else:
return True
okay = entry.register(isOkay) # didn't understand why I had to do this, but did it anyway...
entry = ttk.Entry(mainframe, validate="key", validatecommand=(okay, '%d'))
# the mainframe above is a ttk.Frame that contains all the widgets, and is the only child of the usual root [ = Tk()]
entry.grid(column=1,row=10) # already have lots of stuff on upper rows
The error I'm getting goes like this:
"NameError: name 'entry' is not defined"
I've tried to change the order of things, but there's always one of these errors.. It points to the line where I do the .register() stuff
--EDITED CODE--
This doesn't throw me an error, but still allows me to delete...
def isOkay(why):
if (why == 0):
return False
else:
return True
okay = (**root**.register(isOkay), "%d")
entry = ttk.Entry(mainframe, validate="key", validatecommand=okay)
entry.grid(column=1,row=10)
(where the 'root' part is written between ** **, does it have to be the root? Or it can be any parent of the widget that is going to use that? Or it has to be the immediate parent of it?
for instance, I have:
root >> mainframe >> entry. Does it have to be root, mainframe, or could be both?)
All usages of self are due to the use of classes. They have absolutely nothing to do with the validation. Nothing at all.
Here's an example without using classes, and without the long comment describing the validation function:
import Tkinter as tk
def OnValidate(d, i, P, s, S, v, V, W):
print "OnValidate:"
print "d='%s'" % d
print "i='%s'" % i
print "P='%s'" % P
print "s='%s'" % s
print "S='%s'" % S
print "v='%s'" % v
print "V='%s'" % V
print "W='%s'" % W
# only allow if the string is lowercase
return (S.lower() == S)
root = tk.Tk()
vcmd = (root.register(OnValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
entry = tk.Entry(root, validate="key",
validatecommand=vcmd)
entry.pack()
root.mainloop()
Note: the point of registering a command is to create a bridge between the underlying tcl/tk engine and the python library. In essence it creates a tcl command that calls the OnValidate function, giving it the supplied arguments. This is necessary because tkinter failed to provide a suitable interface to the input validation features of tk. You don't need to do this step if you don't want all of the fancy variables (%d, %i, etc).
The error NameError: name 'entry' is not defined is because you are using entry before you define what entry is. One of the benefits of using classes is that it allows you define methods further down in the file than where you use them. By using a procedural style you are forced to define functions before they are used*.
* technically speaking, you always have to define functions before they are used. In the case of using classes, you don't actually use the methods of a class until after you've created the instance of the class. The instance isn't actually created until very near the end of the file, which lets you define the code well before you use it.

Resources