In this basic Python Tkinter code, I'm trying to bind certain functions to trigger upon either a UI button press or a keyboard key press.
import tkinter as tk
from tkinter import ttk
main_window = tk.Tk()
main_window.title('Test UI')
# Change text with "Enter" then flush
def changeTextEnter():
text_label.configure(text=entry_bar.get())
entry_bar.delete(0, tk.END)
# Close program key function
def quitApp():
main_window.destroy()
# Enter Button
enter_button = ttk.Button(text='Enter', command=changeTextEnter)
enter_button.grid(row=0, column=0)
# Entry bar
entry_bar = ttk.Entry(width=30)
entry_bar.grid(row=0, column=1)
# Quit Button
quit_button = ttk.Button(text='Quit', command=main_window.destroy)
quit_button.grid(row=0, column=2)
# Text label
text_label = ttk.Label(text='TEST TEXT GOES HERE')
text_label.grid(row=1, column=0, columnspan=2)
# Bind enter key
main_window.bind('<Return>', changeTextEnter)
# Bind quit key
main_window.bind('<Escape>', quitApp)
main_window.mainloop()
After a while of trial and error, it seems to work the way I want if I add an
*randomVariable
in the declarations of:
def changeTextEnter(*randomVariable):
and:
def quitApp(*randomVariable):
I get that one asterisk lets the function take an unknown number of arguments, and a double asterisk acts as a dictionary with key values.
My questions are:
- Why do I need a parameter in those functions at all?
- How does the variable "*randomVariable" get used, since it seems like I'm not actually using/ assigning anything to "randomVariable" anywhere within the function.
- Why does the function not work as intended without the asterisk before the variable?
CodePudding user response:
Thanks for your question and for providing a runnable code snippet!
Conclusion (TL;DR)
- You're using the same function to handle two different kinds of events (button push, keystroke).
- Because of this, your function has to handle different numbers of arguments
- Using
*
gives your function this kind of flexibility
About the *
(asterisk)
You correctly mentioned that you can use *
in a function's parameter list to accept any number of positional arguments. You probably have seen some function like this:
def my_func(*args, **kwargs):
...
So now the question is "what is the difference between *args
and args
?" We can find the answer in the Python Language Reference:
An asterisk
*
denotes iterable unpacking. Its operand must be an iterable. The iterable is expanded into a sequence of items, which are included in the new tuple, list, or set, at the site of the unpacking.
The important word here is unpacking. Consider this example:
>>> my_list = [1, 2, 3]
>>> print(my_list)
[1, 2, 3]
>>> print(*my_list)
1 2 3
print(my_list)
just prints the list, nothing special hereprint(*my_list)
actually does two things:- Unpack the list
- Print the elements of the list one-by-one
In other words:
print(my_list)
is equivalent toprint([1, 2, 3])
print(*my_list)
is equivalent toprint(1, 2, 3)
<-- no square brackets
Finding out why you need the *
in your code
Here, I'll talk about the function changeTextEnter()
, but the same applies to quitApp()
.
Basically, you're using changeTextEnter()
for two different things:
- For a Button command:
ttk.Button(..., command=changeTextEnter)
- For a key binding:
main_window.bind(..., changeTextEnter)
Pressing the "Enter" button and hitting the <Return>
key both call changeTextEnter()
, but in a different way (with different arguments).
You can use a debugger to observe this:
def changeTextEnter(*args):
text_label.configure(text=entry_bar.get()) # <-- breakpoint here
entry_bar.delete(0, tk.END)
Another way is to print the value of args
:
def changeTextEnter(*args):
# DEBUG
print(args)
text_label.configure(text=entry_bar.get())
entry_bar.delete(0, tk.END)
Action | Value of args |
---|---|
Pushing the "Enter" button | () (empty tuple) |
Hitting the <Return> key |
(<KeyPress event ...>,) |
You original code can't handle the second case because the function doesn't expect a positional argument:
TypeError: changeTextEnter() takes 0 positional arguments but 1 was given
Side notes
- If you aren't interested in the
<KeyPress event>
object, you can change your function definition to this:def changeTextEnter(*_)
. This will just "swallow" any positional arguments. The_
name is a convention for "I don't care about this" - One of the most prominent functions using
*
is the built-inprint()
function. Did you ever wonder how these calls work?:print()
print("Hello")
print("Cool number:", 42)
print("bla", "bla", end="")
Have a look at the function definition, it uses *
:
print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)