Search code examples
pythontkintertkinter-button

Can a button's command use variables from the function including the button?


I have a function which creates some labels and input fields and a button. I want the button's command function manage objects from this function. Can I do this without making variables global?

Here's the code:

def add_clicked():
    cuspsInfo.append((int(xInput.get()), int(yInput.get()), indexInput.get()))
    xInput.delete(0, END)
    yInput.delete(0, END)
    indexInput.delete(0, END)

def startup_menu():
    global cuspsInfo
    cuspsInfo = []
    global label
    label = Label(window, text="Add a cusp:", font=("Arial", 20, "bold"), 
        fg=SECOND_COLOR, bg=FIRST_COLOR)
    label.place(relx=0.5, rely=0.3, anchor=CENTER)

    global xInput
    global yInput
    global indexInput
    xInput = Entry(window)
    xInput.place(relx=0.52, rely=0.4, anchor=CENTER)
    yInput = Entry(window)
    yInput.place(relx=0.52, rely=0.45, anchor=CENTER)
    indexInput = Entry(window)
    indexInput.place(relx=0.52, rely=0.5, anchor=CENTER)

    global xLabel
    global yLabel
    global indexLabel
    xLabel = Label(window, text="x(0-500):", font=(
        "Arial", 20, "bold"), fg=SECOND_COLOR, bg=FIRST_COLOR)
    xLabel.place(relx=0.34, rely=0.4, anchor=CENTER)
    yLabel = Label(window, text="y(0-500):", font=(
        "Arial", 20, "bold"), fg=SECOND_COLOR, bg=FIRST_COLOR)
    yLabel.place(relx=0.34, rely=0.45, anchor=CENTER)
    indexLabel = Label(window, text="name:", font=(
        "Arial", 20, "bold"), fg=SECOND_COLOR, bg=FIRST_COLOR)
    indexLabel.place(relx=0.36, rely=0.5, anchor=CENTER)

    global addButton
    global runButton
    addButton = Button(window, text="add", command=add_clicked)
    addButton.place(relx=0.45, rely=0.6, anchor=CENTER)
    runButton = Button(window, text="run", command=run_clicked)
    runButton.place(relx=0.55, rely=0.6, anchor=CENTER)

Solution

  • I'd "partially apply" the function. First, change your callback to accept four arguments:

    def add_clicked(x_in, y_in, index_input, cusps_info):
        cusps_info.append((int(x_in.get()), int(y_in.get()), index_input.get()))
        x_in.delete(0, END)
        y_in.delete(0, END)
        index_input.delete(0, END)
    

    Now, wrap the call to that callback in another function, and supply the arguments then. This can be does with a simple lambda:

    addButton = Button(window, text="add", command=lambda: add_clicked(xInput, yInput, indexInput, cuspsInfo))
    

    or with functools.partial:

    from functools import partial
    
    addButton = Button(window, text="add", command=partial(add_clicked, xInput, yInput, indexInput, cuspsInfo))
    

    Both have the same effect: the arguments are "pre-supplied" so that the callback can later be called without any arguments.

    As you can see though, as the amount of arguments increases, this becomes increasingly messy. This technique is best for smaller numbers of arguments. If you have a lot of data needing to be passed into a function, it may make sense to place them into a NamedTuple or a full custom class (if they're all very similar and make sense together).