Why use the pack() method in another line? - python

I have a quick question regarding why I should use .pack() in another line.
Example:
If I call the Label function as follows:
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text="Sample Text").pack()
it has the same effect in tkinter as:
label = tk.Label(root, text="Sample Text")
label.pack()
But as soon I want to configure the label later on, the first example dosenĀ“t work:
label = tk.Label(root, text="Sample Text").pack()
label.config(bg="YELLOW")
raises an:
AttributeError: 'NoneType' object has no attribute 'config'
This is fixed as soon I write the pack() function in a separate line:
label = tk.Label(root, text="Sample Text")
label.pack()
label.config(bg="YELLOW")
Why is python behaving like that?

Because:
All methods return something, be it None or some other
particular object.
call1().call2().call3()....calln() returns whatever calln()
supposed to return.
left-hand side of an assignment (lh = rh) is assigned whatever
expression(if any) in the rh returns.
grid, pack, place are all methods on tkinter widgets that return None (the default return value when the absence of an actual return statement).
Here's a very similar behavior:
def return_a_list():
return [0, 1]
rh = return_a_list().append(3)
lh = rh
input(lh)

This has to do with return values. If you're chaining statements, then your return value is going to be whatever the last method in the chain returns. Label() returns a label object. pack() returns None. You can imagine now what the error "NoneType object has no attribute 'X'" actually means.
So, the solution is, use Label() and save the object to a variable. Then, call the method pack() on the object. pack() still returns None, but the label variable is not overwritten with None. It still contains the label object.
Edit: I figured I'd elaborate a bit.
All functions in python return a value, even if this is not done explicitly. When you write a function like
def return_something():
return "some value"
return_value = return_something()
then the return_value variable will refer to a string with text "some value".
Consider a function that does not return anything like so
def return_nothing():
print "No values returned in this function"
returned_value = return_nothing()
The return_nothing() will have return value of None and that's the value that will be assigned to returned_value. If you had anything in there before, it will be gone.
When doing method chaining like you do in your Label().pack() example, what you're actually doing is calling the method pack() on the object that is returned by Label(). The final return value of the chain will be whatever the last method returns. pack() returns nothing, so the return value is None and that's what is assigned to the variable label. However, if you end the chain with Label() then the return value is a label object and that's what goes in label. When you call pack() on a separate line, the return value None is discarded (it does not overwrite the label object inside label).
Hopefully that's a little more clear :)

Related

How to add two integers dynamically

I'm trying to add two integers dynamically with tkinter. My idea is to get the sum after typing in a integer.
My idea:
import tkinter as tk
root = tk.Tk()
root.geometry("600x100")
v = tk.IntVar()
w = tk.IntVar()
eingabefeld_0 = tk.Entry(master=root, textvariable = v)
eingabefeld_0.place(x=80, y=40)
eingabefeld_1 = tk.Entry(master=root, textvariable = w)
eingabefeld_1.place(x=320, y=40)
label = tk.Label(master=root, textvariable = v)
label.place(x=80, y=80)
label = tk.Label(master=root, textvariable = str(int(v.get())+int(w.get())))
label.place(x=320, y=80)
root.mainloop()
If start the program the result of the addition is not presented in the label. What is missing here?
You aren't using the textvariable option correct. The value passed to that option needs to be an instance of one of the tkinter variable objects (StringVar, IntVar, etc). You can't just pass an expression to it.
You need to write a function that calculates the result and sets the value of this variable. The function can then be called from a button, or you can set a trace on the variable and update it every time the value changes. You'll have to make sure to take care of the case where one of the values you're trying to add is not a valid integer.
For example, start by creating a new variable for the result, and using it in your label. In this case I recommend StringVar so that we can display something like "???" if we can't compute the value.
resultvar = tk.StringVar()
label = tk.Label(master=root, textvariable = resultvar)
Next, create a function that computes the result and stores it in this variable. You need to use try/except since it's possible for the user to delete everything in the entry widget in which case the call to .get() will fail.
def compute_result():
try:
result = v.get() + w.get()
resultvar.set(result)
except Exception as e:
resultvar.set("???")
Now you just need to add code that calls this function. You can do it several ways:
you can bind to the return key so that the result is updated when the user presses that key
you can bind to any key release so that the result is updated when the user presses and releases any key
you can add a button that the user has to click to update the value
you can add a trace to the input variables, which will cause a function to be called whenever the values change
To use a trace, call trace_variable telling it when to do the trace ("w" means whenever the value is written/changed), and what function to call. The trace will append some arguments to the function that we don't use, so we'll have to change the function to accept these arguments
def compute_result(*args):
try:
result = v.get() + w.get()
resultvar.set(result)
except Exception as e:
resultvar.set(f"???")
v.trace_add("write", compute_result)
w.trace_add("write", compute_result)
With that, whenever the value in the variable v or w changes, the function will be called and the result variable will be updated.

Changing a canvas object's color using a button tkinter

I am having trouble getting a button to work as intended in python 3. That being, that when it is pressed it changes the color of the circle from one color to another, beginning in red. However, when I run this program it simply begins as white instead of red nor does pressing the buttons change the color of the circle and I am very confused as to why.
from tkinter import *
def changeColor():
test.itemconfig(circle, fill = "blue")
def changeColor2():
test.itemconfig(circle, fill = "white")
root = Tk()
test = Canvas(root, width = 50, height = 50)
test.pack()
circle = test.create_oval(0, 0, 25, 25,fill="red")
button = Button(root,text="",command=changeColor(),bg= "blue")
button2= Button(root,text="",command=changeColor2(),bg= "white")
button.pack()
button2.pack()
root.mainloop()
From Wikibooks on Python
Closures are possible in Python because functions are first-class objects. A function is merely an object of type function. Being an object means it is possible to pass a function object (an uncalled function) around as argument or as return value or to assign another name to the function object. A unique feature that makes closure useful is that the enclosed function may use the names defined in the parent function's scope.
The syntax for passing a function is the function name without the brackets:
command=changeColor

What exactly is the difference between 'Lambda formats'?

First of all, I'm a begginer in Python, so get ready for a nooby question ))
In one of the topics on this site I've found quite a useful piece of advice concerning the use of lambda function.
Here's my code before correcting:
def entree1(self): #function that is supposed to change text in
self.configure(text = "X") the label block from whatever it is to 'X'
fen = Tk()
pole1 = Label(fen, text = '|_|')
pole1.bind("<Button-1>", lambda: entree1(pole1)) #event handler reffering to the function above
Here's my code after correcting:
def entree1(self): #function that is supposed to change text in
self.configure(text = "X") the label block from whatever it is to 'X'
fen = Tk()
pole1 = Label(fen, text = '|_|')
pole1.bind("<Button-1>", lambda x: entree1(pole1)) #event handler reffering to the function above
In a nutshell I changed lambda: some func to lambda x: some func.
And it worked, which is great, although I can't figure out the difference between this two variants.
Could you please tell me what exactly changed after I added x?
Thank you for your time!
Let me translate lambda expressions to function definitions that you are probably more used to:
lambda : entree1(pole1)
is the same as
def lambdaFunc():
global pole1
return entree1(pole1)
Your corrected function is
lambda x : entree1(pole1)
which is the same as
def lambdaFunc(x):
global pole1
return entree1(pole1)
You need the extra argument because Tk buttons call the function they are bound to with a variable (I forget what the variable is, exactly), therefore calling a function with an input variable when it doesn't take one causes errors.

Binding events to widget not working

I have a function, say foo and it's supposed to bind an event
def foo:
for x in widget_list:
widget.config(command = lambda: self.function(widget_list.index(x))
def function(index):
do something here....
print index
The widget list contains buttons, and whenever I click corresponding buttons, they're supposed to print to the IDLE their index, but what happens is that all the buttons I click end up printing the last index of widget_list. I'm guessing that while for iterates, the argument of the function also changes thus only the last index is preserved. Is there anyway to bind the previous indexes to the button?
The simplest fix is to make sure that your lambda is using the current value of x:
widget.config(command = lambda x=x: self.function(widget_list.index(x))
This forces x to be a default parameter to the function, and the default value will be the value of x at the time the lambda is defined.

Python Tkinter button callback

I am trying to print out the button number when i click each buttons that are created by for loop. The following is what i have tried.
import Tkinter as tk
root=tk.Tk()
def myfunction(a):
print a
for i in range(10):
tk.Button(root,text='button'+str(i),command=lambda:myfunction(i)).place(x=10,y=(10+(25*i)))
root.mainloop()
But instead of printing out each button number, it actually giving me the last button number everytime. Is there anything i can do so that when i click button 1, it will print 1,2 for 2 ,and so on?
Easy fix is to initialize the lambda function with the current value of i each time the lambda function is created. This can be done using Python default values for another dummy variable j.
command = lambda j=i: myfunction(j)
Blender's answer is a clever solution but in case you get thrown off by the function abstraction, here is another possible way to do it. It really just creates a mapping, saved in buttons, from Button widgets to their proper numbers.
import Tkinter as tk
root = tk.Tk()
def myfunction(event):
print buttons[event.widget]
buttons = {}
for i in range(10):
b = tk.Button(root, text='button' + str(i))
buttons[b] = i # save button, index as key-value pair
b.bind("<Button-1>", myfunction)
b.place(x=10,y=(10+(25*i)))
root.mainloop()
The problem is that the Button command in tk ignores any parameters so insomething like mycommand(data) the data is ignored.
I use buttons quite a bit and decided to subclass a tk Button to include data. I called it a DataButton and added a data member ( in this case an index number). That way when clicked it passes back its data. ( index number) Now I use the DataButton whenever I want it to hold information like an index or even a message.
It's because i in your anonymous function refers to the counter variable, not to the value:
from __future__ import print_function
x = [lambda: print(i) for i in range(10)]
for f in x:
f()
This produces an output of 10 consecutive 9s.
To get around this, you'd have to use two lambdas and shadow i with a second function (which creates your first function):
from __future__ import print_function
x = [(lambda i: (lambda: print(i)))(i) for i in range(10)]
for f in x:
f()
Although at that point, you'd be better off just making a named function:
def my_command(i):
def inner_function():
return my_function(i)
return inner_function
And using it like this:
tk.Button(root, text='button' + str(i), command=my_command(i))

Categories