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?
Advertisement
Answer
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)