0

I am making a rock paper scissors program and I need to change whose turn it is when they click a button, but I do not want to use the global keyword because the program is inside of a function.

Here is an example of what I am trying to do without using the global keyword:

from tkinter import *
root = Tk()

var = 1

def buttonClick():
    global var
    var += 1
    print(var)

button = Button(root, text="button", command=buttonClick).pack()
root.mainloop()

I have tried to write command=(var += 1) but that did not work.

martineau
  • 119,623
  • 25
  • 170
  • 301
Peter
  • 21
  • 4
  • If the program is inside of a function then the code in your question is not a [mre] of it — so I suggest you fix that. – martineau Mar 02 '22 at 18:11

3 Answers3

2

If the whole script is inside a function (including the buttonClick() function) then use the nonlocal keyword:

def buttonClick():
    nonlocal var
    var += 1
    print(var)

If the function is not nested, the only way is to create a global variable and the global keyword in both functions.

Lecdi
  • 2,189
  • 2
  • 6
  • 20
1

No, you indeed can't. It would be possible to change the contents of a global varif it were a list, for example. And then you could write your command as a lambda expression with no full function body.

But this is not the best design at all.

Tkinter event model couples nicely with Python object model - in a way that instead of just dropping your UI components at the toplevel (everything global), coordinated by sparse functions, you can contain everything UI related in a class - even if it will ever have just one instance - that way your program can access the var as "self.var" and the command as "self.button_click" with little danger of things messing up were they should not.

It is just that most documentation and tutorial you find out will have OOP examples of inheriting tkinter objects themselves, and adding your elements on top of the existing classes. I am strongly opposed to that approach: tkinter classes are complex enough, with hundreds of methods and attributes -wereas even a sophisticated program will only need a few dozens of internal states for you to worry about.

Best thing is association: everything you will ever care to access should eb a member of your class. In the start of your program, you instantiate your class, that will create the UI elements and keep references to them:

import tkinter as tk # avoid wildcard imports: it is hard to track what is available on the global namespace

class App:
    def __init__(self):
        self.root = tk.Tk()
        self.var = 1
        # keep a refernce to the button (not actually needed, but you might)
        self.button = tk.Button(self.root, text="button", command=self.buttonClick)
        self.button.pack()

    def buttonClick(self):
        # the button command is bound to a class instance, so
        # we get "self" as the object which has the "var" we want to change
        self.var += 1
        print(self.var)

    def run(self):
        self.root.mainloop()


if __name__ == "__main__": # <- guard condition which allows claases and functions defined here to be imported by larger programs
    app = App()
    app.run()

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • You said that you can't change it, but surely you can using the `nonlocal` keyword? Unless I have misunderstood the question. I do think your approach is better overall though – Lecdi Mar 02 '22 at 18:32
  • 1
    FWIW, in a `tkinter` application, making the global variable an [`IntVar`](https://web.archive.org/web/20201031100214id_/https://effbot.org/tkinterbook/variable.htm) would allow it to be changed by using its `set()` method (without needing to declare it `global` beforehand). – martineau Mar 02 '22 at 18:47
0

Yes, you can. Here's a hacky way of doing it that illustrates that it can be done, although it's certainly not a recommended way of doing such things. Disclaimer: I got the idea from an answer to a related question.

from tkinter import *

root = Tk()
var = 1
button = Button(root, text="button",
                command=lambda: (globals().update(var=var+1), print(var)))
button.pack()
root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
  • But surely this won't work for the same reason `global` won't work?- all of the code is in a function – Lecdi Mar 02 '22 at 18:37
  • @Lecdi: This works if the variable is global as it currently is in the *code* in the OP's question (which they say is "is an example of what I am trying to do"). – martineau Mar 02 '22 at 18:41