Instance method takes 1 positional argument but 2 were given! But no argument is given!

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):
  def initializeUI(self):
    self.title("Simple Games")
    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))

    self.pc_input = StringVar()

    self.ddVal = StringVar()

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

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:

1 Like

A bit curios. Can you try:

import tkinter as tk

class RSP(tk.TK):
1 Like

Thanks. I think that’s probably what’s happening. I suppose the mixture of python and tcl (the tkinter language) must sometimes produce some minor anomalies.

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.

1 Like

See if this is more in tune to what you are looking for. You can modify the contents of the dictionary and/or the contents of the methods.

import tkinter as tk

class RSP(tk.Tk):
  def __init__(self):

    # 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)) = {'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

  def choice(self, *args):

      pc_input = self.pc_input.get()[pc_input]()
  def rock(self):
      print('Selected rock!')

  def paper(self):
      print('Selected paper!')
  def scissors(self):
      print('Selected scissors!')      

app = RSP()
1 Like

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.

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.

1 Like

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.

No, I mean, right now you use:

But you can do it more easily:

  def choice(self):

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:

So, to verify, temporarily substitute the body of the choice method with this one and run it:

  def choice(self, *args):


      pc_input = self.pc_input.get()

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

1 Like

I wouldn’t call it ‘more easily’. It’s a slightly different way but it’s worth knowing.

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: