Home > OS >  Why do I need an asterisk and some random variable in these Python Tkinter functions for them to wor
Why do I need an asterisk and some random variable in these Python Tkinter functions for them to wor

Time:08-05

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?

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 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)

  • Related