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 here
- print(*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 to- print([1, 2, 3])
- print(*my_list)is equivalent to- print(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)
