The following class code is an extract from a tkinter window to create a simple game ‘roc, scissors, paper’. There is one peculiar error that I can only resolve only by ‘implementing’ this error.
class RSP(Tk):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
self.title("Simple Games")
self.geometry("400x200+0+0")
self.setupWindow()
self.attributes('-topmost', True)
self.choices = {0:'roc', 1:'scissors', 2:'paper'}
def choice(self):
self.pc_input.set(self.choices[rand.randint(0, 2)])
def setupWindow(self):
title = Label(self, text='Play Roc, Scissors & Paper', pady=20, justify='center', font=('Calibri', 12))
title.pack()
self.pc_input = StringVar()
self.pc_input.set('')
self.ddVal = StringVar()
self.ddVal.set('')
Label(self, text='', textvariable=self.pc_input, justify='left').place(x=115, y=100)
OptionMenu(self, self.ddVal, *['roc', 'paper', 'scissors'], command=self.choice).place(x=115, y=70)
app = RSP()
app.mainloop()
The error is: “TypeError: RSP.choice() takes 1 positional argument but 2 were given”.
As you can see from the code of choice, there is no argument except self.
I can resolve this by actually adding one (redundant) argument to the function:
def choice(self, x):
self.pc_input.set(self.choices[rand.randint(0, 2)])
The argument x is not used at all but Python accepts it and the class runs ok.
How can this be? Your help is much appreciated.
I’m guessing that because you gives choice(...)
to Tkinter, then the Tkinter expects your function also receives some kind of an event object.
In GUI systems in particular I saw that they very often (almost always) if they allows you to set callbacks, then the GUI system calls your function with an extra argument.
This argument may be anything: from an event object to your custom value.
So, tldr You must keep that extra argument, even that you find now no use of it. If you are curious about what the argument is, then You can put:
print(type(x), repr(x))
inside your method ![:slight_smile: :slight_smile:](https://emoji.discourse-cdn.com/apple/slight_smile.png?v=12)
Thanks but that makes no difference. I usually import ‘’‘tkinter & Tk’‘’ with:
import tkinter as tk
from tkinter import Tk
It seems that an argument is expected anyway with this callback function, choice, as Przemyslaw pointed out.
choice
is an ‘instance method’ because its first parameter is an instance, self
. A ‘class method’ would take the class as the first parameter.
I read the tkinter.__init__
code for OptionMenu and its helper function, which wraps the command passed to OptionMethod. The second parameter is the choice you clicked, and which is assigned, in your case, to self.ddval
. It just happens that your command callback does not need it, but tkinter does not know that. So you must write the function to accept it. Just like when writing an event callback that ignores the particulars of the event but must have an even
parameter to accept it.
See if this is more in tune to what you are looking for. You can modify the contents of the self.menu
dictionary and/or the contents of the methods.
import tkinter as tk
class RSP(tk.Tk):
def __init__(self):
super().__init__()
# Define window and titles
tk.Tk.title(self, "Simple Games")
tk.Tk.geometry(self, "400x200")
self.title = tk.Label(self, text = 'Play Roc, Scissors & Paper',
pady = 20, justify = 'center', font = ('Calibri', 12))
self.menu = {'Rock': self.rock, 'Scissors': self.scissors, 'Paper': self.paper}
self.pc_input = tk.StringVar()
self.pc_input.trace('w', self.choice) # state callback method
menu_choices = ('Rock', 'Paper', 'Scissors')
self.option = tk.OptionMenu(self, self.pc_input, *menu_choices)
self.pc_input.set(menu_choices[0]) # Set default choice
self.title.pack()
self.option.pack()
def choice(self, *args):
pc_input = self.pc_input.get()
self.menu[pc_input]()
def rock(self):
print('Selected rock!')
def paper(self):
print('Selected paper!')
def scissors(self):
print('Selected scissors!')
app = RSP()
app.mainloop()
Reef:
The error is: “TypeError: RSP.choice() takes 1 positional argument but 2 were given”. As you can see from the code of choice, there is no argument except self.
There is one parameter, because self
counts. The parameters are the things that tell you how many arguments are needed. The arguments are the things supplied when the function is called.
Reef:
OptionMenu(self, self.ddVal, *['roc', 'paper', 'scissors'], command=self.choice).place(x=115, y=70)
Nothing in your code actually calls the function. Instead, by writing command=self.choice
here, you tell Tkinter to call self.choice
when the menu is interacted with.
When Tkinter calls self.choice
, there will be two arguments to RSP.choice
, exactly as the error says. The first is self
, because you are using a method rather than a plain function. The other is an object that Tkinter creates to give you more information about the event
that occurred (for example, the exact position of a mouse click, which option was selected in a widget that shows options, etc.).
As an aside: rock is a stone; “roc” is a mythical giant bird, and not normally part of this game. Also, the random
module lets you make a random.choice
from a list directly - you don’t need to write that logic yourself.
# Define window and titles
tk.Tk.title(self, "Simple Games")
tk.Tk.geometry(self, "400x200")
self.title = tk.Label(self, text = 'Play Roc, Scissors & Paper',
pady = 20, justify = 'center', font = ('Calibri', 12))
self.menu = {'Rock': self.rock, 'Scissors': self.scissors, 'Paper': self.paper}
self.pc_input = tk.StringVar()
self.pc_input.trace('w', self.choice) # state callback method
menu_choices = ('Rock', 'Paper', 'Scissors')
self.option = tk.OptionMenu(self, self.pc_input, *menu_choices)
self.pc_input.set(menu_choices[0]) # Set default choice
self.title.pack()
self.option.pack()
def choice(self, *args):
pc_input = self.pc_input.get()
self.menu[pc_input]()
def rock(self):
print('Selected rock!')
def paper(self):
print('Selected paper!')
def scissors(self):
print('Selected scissors!')
Thanks. However the redundant parameter *args must be there for your code to work.
My question is about understanding why we need to add a (useless) parameter to the function which seemingly paradoxically resolves the error of ‘1 positional arg required but 2 args given despite no args given beyond the implicit self’. The question is more or less answered by others in this thread.
Agreed on parameter instead of argument and that ‘roc’ or ‘rook’ is a mythical giant bird that Sindbad clings to to escape form danger.
The insight that I get from the various answers is that tkinter creates another object to relate to the event.
Regarding the logic, basically when we click and choose one of the 3 choices (from the dropdown) there’s a call to the choice function which assigns a random input using rand.randint(0, 2) where I imported random as rand.
Reef:
Regarding the logic, basically when we click and choose one of the 3 choices (from the dropdown) there’s a call to the choice function which assigns a random input using rand.randint(0, 2) where I imported random as rand.
No, I mean, right now you use:
Reef:
def choice(self):
self.pc_input.set(self.choices[rand.randint(0, 2)])
But you can do it more easily:
def choice(self):
self.pc_input.set(rand.choice(self.choices))
Note that in the following line of code, we have declared our callback method
to be choice
.
self.pc_input.trace('w', self.choice) # state callback method
The w
write mode invokes the callback whenever the value of the pc_input
variable changes.
We then tie that method (function) to the drop-down menu via this line of code (notice the 2nd argument):
self.option = tk.OptionMenu(self, self.pc_input, *menu_choices)
Thus, every time that there is a change in the selection to the drop-down menu, a tuple is generated - the following tuple:
('PY_VAR0', '', 'w')
The PY_VAR0
variable holds the selection value. In order to obtain its value, we must use the self.pc_input.get()
operation. Although not explicitly shown in the code, it is visiable to the code as verified by the .get
operation.
You can also verify by the following print statement:
print(pc_input)
So, to verify, temporarily substitute the body of the choice
method with this one and run it:
def choice(self, *args):
print('\n',args)
pc_input = self.pc_input.get()
print(pc_input)
self.menu[pc_input]()
One additional small point to make. Notice that if we only use a print statement inside the body of the method, then no extra *args
argument is needed in the header of the method definition. But since we are making a call to the contents of the PY_VAR0
variable implicitly, we have to make use of the *args
argument.
def choice(self):
print('Hello, inside the choice method')
The insight that I get from your response is that tkinter adds another parameter to the choice method but the class only sees the self parameter and that’s why it gives the error of ‘1 positional arg but 2 were given’. By adding a redundant parameter x to choice (def choice(self, x)) to ‘make up’ for the tkinter extra parameter, the error disappears. This also explains your comment ‘Nothing in your code actually calls the function’.
Thanks Paul. I wasn’t aware of the trace method in
self.pc_input.trace('w', self.choice) # state callback method
Further learning for me ![:slight_smile: :slight_smile:](https://emoji.discourse-cdn.com/apple/slight_smile.png?v=12)