Python Forum
[Tkinter] Passing on values
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Passing on values
#1
Hi, I'm new to Python and tkinter programming. I have tried to find an answer to my problem on this forum, but couldn't find it. This problem is surely a most common problem, but still I am not able to solve it! Cry

I am trying to pass on variables that contain choices made with dropdown menu's, radio buttons and so on. Every time I try this I get "0" (or "PY_VAR0" if I didn't put "choice = 0 " at the beginning of the script) as a result.

So I tried a simple script to bring things back to the essence but with the same result. My code:

from tkinter import *

master = Tk()

choice = IntVar()
choice = 0

def callback1():
    print ("Your choice is number 1!")
    choice = 1
    
def callback2():
    print ("Your choice is number 2!")
    choice = 2
    
def callback3():
    print ("Your choice is number 3!")
    choice = 3


def callback6():
    print ("Your final choice is: ")
    print (choice)
    

b = Button(master, text="Choice 1", command=callback1)
b.grid()

b = Button(master, text="Choice 2", command=callback2)
b.grid()

b = Button(master, text="Choice 3", command=callback3)
b.grid()


b = Button(master, text="Let's show what choice you made!", command=callback6)
b.grid()

master.mainloop() 
When I make a choice: say "Choice 2", when I press the last button I expect my choice to be "2". Instead it is always "0", no matter what choice I made before.

What am I doing wrong?
Reply
#2
I assume 'last button' is 'show choice'
Choice has to be set and get.
code like:
from tkinter import *

master = Tk()

choice = IntVar()

def callback1():
    print ("Your choice is number 1!")
    choice.set(1)
    
def callback2():
    print ("Your choice is number 2!")
    choice.set(2)
    
def callback3():
    print ("Your choice is number 3!")
    choice.set(3)


def callback6():
    print ("Your final choice is: ")
    print (choice.get())
    

b = Button(master, text="Choice 1", command=callback1)
b.grid()

b = Button(master, text="Choice 2", command=callback2)
b.grid()

b = Button(master, text="Choice 3", command=callback3)
b.grid()


b = Button(master, text="Let's show what choice you made!", command=callback6)
b.grid()

master.mainloop() 
DurkVell likes this post
Reply
#3
This is the most commonly asked and answered question in Python. You weren't able to find the answer because you don't know how to ask the question right. Don't feel bad. If you knew how to ask the question you would already know the answer. Yeah, it is one of those questions.

The question is easier to answer with a simpler example:
choice = 0

def set_choice_one():
    choice = 1

def set_choice_n(n):
    choice = n

def print_choice():
    print(choice)

print_choice()
set_choice_one()
print_choice()
set_choice_n(2)
print_choice()
Output:
0 0 0
The answer to your question about why is "choice" always zero is; well, it depends on what choice you are talking about.

There are three variables named "choice" in the example above. Think of these variables as module.choice, set_choice_one.choice and set_choice_n.choice. At the top of the program, when Python executes choice = 0, the variable module.choice is created. When python sees a name and an assignment (=), it starts looking for a variable. Because the assignment is occurring outside a function or class, Python starts looking for the variable "choice" in something called the "module namespace". All modules (files) get their own namespace where their variables can hide. If Python didn't hide module variables in the module namespace it would be impossible to write programs. Every time you needed a variable or function you would have to search your entire Python distribution to find a new, unique name. The module namespace hides all those other variable names used in other modules so you can use almost any variable name you want.

Modules aren't the only thing to have a namespace. Functions have a namespace too. When set_choice_one() executes choice = 1, Python does the same thing it did for choice = 0, but this time it does it in the function namespace. Python looks around for "choice" in the set_choice_one() namespace, doesn't find a variable with that name and creates a variable named "choice".

You may be wondering about print_choice(). print_choice() uses a variable named "choice", and since it prints "0" it must be using the module.choice. How does it find that?

Unlike assignment (=), getting the value of a variable does not automatically create a new variable if the name is not found. Instead, Python looks starts looking outside at other namespaces to see if they define the variable. First Python looks in the function for the variable. If not found it looks in the enclosing function's namespace for the variable. Since we didn't wrap print_choice() inside another function there are no enclosing functions to check. Next Python looks for the variable in the module. "choice" is defined in the module, so print_choice() uses that variable. If Python didn't find choice in the module it looks one last place before giving up; the built-ins. This last step is why I can use print(choice) in my program without getting "NameError: print not found".

Sometimes you want your function to not get a module variable's value, but set a module variable's value. That is what "global" is used for. "global" tells Python to look outside the immediate namespace before creating a new variable.

Another way to accomplish the same thing is to change a value inside a container object. That is what Larz60+ is doing with the IntVar. His callback1() does not change the value of "choice", so Python does not create a local variable. His function changes the value that "choice" contains. This same idea could be implemented using a list or a dictionary.
Well, you did ask how to "pass on variables", so I'll try to answer that question.

There are lots of ways you can pass values around. This example reuses the "choice" example with a spin. Instead of writing a unique callback for every button it reuses a callback and passes along a variable. Actually these examples don't reuse the callback. They create a new function that calls the same callback.
from tkinter import *
from functools import partial
 
master = Tk()
choice = 0
 
def set_choice(value):
    global choice
    choice = value
     
b = Button(master, text="Choice 1", command=partial(set_choice, 1))
b.grid()
 
b = Button(master, text="Choice 2", command=lambda x=2: set_choice(x))
b.grid()
 
b = Button(master, text="Let's show what choice you made!", command=lambda : print(choice))
b.grid()
 
master.mainloop()
Output:
0 1 2
The "Choice 1" button uses a partial function to create a new function that calls setChoice(1). The "Choice 2" button uses a lambda function to call set_choice() and passes the variable "x" which was assigned the value 2. The print button reuses the builtin print() function and passes the module variable "choice".

Another way to pass information around is use tkinter variables. You tried to do this in your example, and Larz60+ did this successfully in his example. I don't write a lot of tkinter code, but when I do I almost always use tkinter variables instead of talking to the widgets directly.

Yet another way to pass information around is to write classes. Classes are a bit like modules in that they have a namespace and can have variables and functions (called methods when talking about a class). Classes can be a good way to organize variables and functions when your program starts growing large.
Reply
#4
Thank you very much for your excellent and elaborated help! I will try to implement your solution in my script.
Reply
#5
Use the particl() function.
here is an example from my code
helpmenu.add_command(label="About...", command=partial(show_about, root))
show_about() is a function like your callback() function.
root is the handle to the Tk root.
The partial() function allows you to pass parameters to callback functions.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020