Skip to content
Advertisement

Why do I need an asterisk and some random variable in these Python Tkinter functions for them to work properly?

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:

  1. Why do I need a parameter in those functions at all?
  2. How does the variable “*randomVariable” get used, since it seems like I’m not actually using/ assigning anything to “randomVariable” anywhere within the function.
  3. 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:
    1. Unpack the list
    2. 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-in print() 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)

Advertisement