Search code examples
pythonclasstkintercombobox

TypeError: action() takes 1 positional argument but 2 were given


I used to work with this way of coding and it worked fine, but after going back to it a few weeks later, it does not anymore. I simplidied my code so it is easy to type here.

import tkinter as tk
from tkinter import ttk

class wind(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        
        # id shutter
        self.SOURCE_SHUTTER = "/dev/ttyUSB0"

        # menu deroulant
        self.listeFlux = ["/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2", "/dev/ttyUSB3"]
        self.listeCombo = ttk.Combobox(self, values=self.listeFlux)
        self.listeCombo.current(0)
        self.listeCombo.bind("<<ComboboxSelected>>", self.action)
        self.listeCombo.pack(side="top")
        
     def action(self):
        self.SOURCE_SHUTTER = self.listeCombo.get()
        print(self.SOURCE_SHUTTER)

if __name__ == "__main__":
    win = wind()
    win.geometry("800x600")
    win.mainloop()

This code gives me the error : TypeError: action() takes 1 positional argument but 2 were given. Does someone know why ? I have seen people make this mistake but their error was that a parameter was missing "self" somewhere in their code, which I don't think I am forgetting here.

Thanks a lot for your help. Valentin

I tried looking in another topic that had the same problem but mine seems different here.


Solution

  • Event bindings in tkinter will inherently pass an event parameter to the bound function, so you'll need deal with it one way or another

    Option 1

    Add an _event parameter to the action method.

    The leading underscore _ is convention to let people know that the value is unused by the function, and event is the conventional name for the event argument taken by event-driven functions in tkinter. The default value of None isn't strictly necessary, but it's good practice.

    def action(self, _event=None):
        self.SOURCE_SHUTTER = self.listeCombo.get()
        print(self.SOURCE_SHUTTER)
    
    Option 2

    Use *_args in your method definition to allow it to accept any number of arguments (as suggested in @3ddavies' answer!). Again, the _ is convention for unused values, and args is convention for this type of parameter. As has been mentioned, the caveat here is that now your action method will accept any number of arguments - this is unlikely to be an issue in this particular case, but keep it in mind!

    def action(self, *_args):
        self.SOURCE_SHUTTER = self.listeCombo.get()
        print(self.SOURCE_SHUTTER)
    
    Option 3

    Use a lambda to absorb the event and call action as an anonymous function

    def __init__(self):
        ...  # code omitted for brevity
        self.listeCombo.bind("<<ComboboxSelected>>", lambda _event: self.action)
        ...
    
    def action(self):  # NOTE: the 'event' parameter is no longer required here
        self.SOURCE_SHUTTER = self.listeCombo.get()
        print(self.SOURCE_SHUTTER)