Python Forum

Full Version: Whys is asterisk and random variable necessary in these functions?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
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?
When you bind the callback to a key, it receives a KeyPress event parameter every time the key is pressed. On the other hand, a function bound to a Button does not receive a parameter when the button is pressed. So if you want to bind the callbacks both to a key and to a Button, you need to give them a variable number of arguments. Try adding a print() in the functions to see what it does
# Change text with "Enter" then flush
def changeTextEnter(*args):
    print('changeTextEnter:', args)
    text_label.configure(text=entry_bar.get())
    entry_bar.delete(0, tk.END)
 
# Close program key function
def quitApp(*args):
    print('quit:', args)
    main_window.destroy()
Output:
changeTextEnter: (<KeyPress event keysym=Return keycode=36 char='\r' x=89 y=14>,) changeTextEnter: () quit: (<KeyPress event keysym=Escape keycode=9 char='\x1b' x=181 y=14>,)
The function signature is misleading. Don't use the asterisk, if you expect a static number of arguments. The callback sends one argument, which is the event itself. You should change the callbacks (example taken form Gribouillis).

# Change text with "Enter" then flush
def changeTextEnter(event):
    print('changeTextEnter:', event)
    text_label.configure(text=entry_bar.get())
    entry_bar.delete(0, tk.END)
  
# Close program key function
def quitApp(event):
    print('quit:', event)
    main_window.destroy()
You should read the documentation. This describes what arguments are passed to the bound event callback.

https://docs.python.org/3/library/tkinte...d-events-1

In your example you are binding a key code (<Return>, <Escape>), so the argument passed to your functions is an event that contains the key that was pressed.

This is a little program that displays the event in an Entry widget. You can select the entry and type and it works like normal, but it you press <Return> or <Escape> an text is replaced by the event info.
import tkinter as tk

class MainWindow(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title("Test UI")
        self.bind("<Return>", self.keypress)
        self.bind("<Escape>", self.keypress)
        self.text = tk.StringVar(self, "")
        tk.Entry(self, textvariable=self.text, width=80).pack(padx=20, pady=20)

    def keypress(self, event):
        self.text.set(str(event))


MainWindow().mainloop()
If you don't use the event you can ignore it. The linter in my IDE does not like functions arguments that aren't used in the function, so I use a place holder instead of an argument.
   def keypress(self, _):
        self.text.set("something happened")
The "_" in the function declaration tells the reader that one argument is passed, but not used by the function. Unfortunately you can only use one "_". If there are multiple arguments to ignore I might do this:
def keypress(self, *_):
But I cannot do this:
def keypress(self, *_, **_):
I can also use a lambda expressing to remove or replace the event argument.
self.bind("<Return>", lambda event:self.keypress("Return was pressed")
self.bind("<Escape>", lambda event:self.keypress("Escape was pressed")

    def keypress(self, msg):
        self.text.set(msg)
Here the lambda expression consumes the "event" argument and passes along a different argument.
(Aug-04-2022, 07:10 PM)deanhystad Wrote: [ -> ]But I cannot do this:

1 def keypress(self, *_, **_):

You should read the error messages:
Error:
SyntaxError: duplicate argument '_' in function definition
Fix:

class Test:
    def keypress(self, *args, **kwargs):
        ...
You could also do
def changeTextEnter(event=None):
    ...
This allows zero or one argument.