Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. transmitting extra data with signals that already sends extra data
Forum Updated to NodeBB v4.3 + New Features

transmitting extra data with signals that already sends extra data

Scheduled Pinned Locked Moved Solved Qt for Python
11 Posts 2 Posters 3.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • E efremdan1

    My project has code similar to the code posted at https://www.learnpyqt.com/tutorials/transmitting-extra-data-qt-signals/:

    from PySide2.QtWidgets import (
        QApplication, QMainWindow, QAction, QPushButton,
        QWidget, QLabel, QVBoxLayout, QHBoxLayout
    )
    
    import sys
    
    
    class Window(QWidget):
    
        def __init__(self):
            super().__init__()
    
            v = QVBoxLayout()
            h = QHBoxLayout()
    
            for a in range(10):
                button = QPushButton(str(a))
                button.pressed.connect(
                    lambda val=a: self.button_pressed(val)
                )
                h.addWidget(button)
    
            v.addLayout(h)
            self.label = QLabel("")
            v.addWidget(self.label)
            self.setLayout(v)
    
        def button_pressed(self, n):
            self.label.setText(str(n))
    
    
    app = QApplication(sys.argv)
    w = Window()
    w.show()
    app.exec_()
    

    I want to use the clicked signal instead of the pressed signal. However, if you change the line button.pressed.connect(lambda val=a: self.button_pressed(val) to button.clicked.connect(lambda val=a: self.button_pressed(val), the code no longer works. This seems to be because the clicked signal emits a checked variable (see https://doc.qt.io/qtforpython-5.12/PySide2/QtWidgets/QAbstractButton.html#PySide2.QtWidgets.PySide2.QtWidgets.QAbstractButton.clicked), which is what ends up getting sent to the lambda function.

    Is there any other way to do this with lambda functions, or do I need to switch over to use functools as per https://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot? button.clicked.connect(partial(self.button_pressed, a)) works, but I'd much rather stick with lambda functions if possible since it looks better to me.

    JonBJ Online
    JonBJ Online
    JonB
    wrote on last edited by JonB
    #2

    @efremdan1 said in transmitting extra data with signals that already sends extra data:

    lambda val=a: self.button_pressed(val)

    Try:

    lambda checked, val=a: self.button_pressed(val)
    

    ?

    Assuming that works(!), I think the Python lambda rule for slots is:

    • First specify as many parameters as the signal sends. You do not have to actually use these in the lambda body if you are not interested in them, but you must specify them to match up to what the signal sends. (You may omit these iff you neither use them nor want to add your own extra parameters.)
    • After these you can then specify as many extra a=bs as you wish to pass your additional parameters.
    E 1 Reply Last reply
    0
    • JonBJ JonB

      @efremdan1 said in transmitting extra data with signals that already sends extra data:

      lambda val=a: self.button_pressed(val)

      Try:

      lambda checked, val=a: self.button_pressed(val)
      

      ?

      Assuming that works(!), I think the Python lambda rule for slots is:

      • First specify as many parameters as the signal sends. You do not have to actually use these in the lambda body if you are not interested in them, but you must specify them to match up to what the signal sends. (You may omit these iff you neither use them nor want to add your own extra parameters.)
      • After these you can then specify as many extra a=bs as you wish to pass your additional parameters.
      E Offline
      E Offline
      efremdan1
      wrote on last edited by
      #3

      @JonB said in transmitting extra data with signals that already sends extra data:

      Try:
      lambda checked, val=a: self.button_pressed(val)

      Doesn't work. Upon clicking, Python sends the error:
      TypeError: <lambda>() missing 1 required positional argument: 'checked'

      Any other ideas?

      JonBJ 2 Replies Last reply
      0
      • E efremdan1

        @JonB said in transmitting extra data with signals that already sends extra data:

        Try:
        lambda checked, val=a: self.button_pressed(val)

        Doesn't work. Upon clicking, Python sends the error:
        TypeError: <lambda>() missing 1 required positional argument: 'checked'

        Any other ideas?

        JonBJ Online
        JonBJ Online
        JonB
        wrote on last edited by
        #4

        @efremdan1
        I will check this out soon in Python and replace this answer with the correction shortly....

        1 Reply Last reply
        0
        • E efremdan1

          @JonB said in transmitting extra data with signals that already sends extra data:

          Try:
          lambda checked, val=a: self.button_pressed(val)

          Doesn't work. Upon clicking, Python sends the error:
          TypeError: <lambda>() missing 1 required positional argument: 'checked'

          Any other ideas?

          JonBJ Online
          JonBJ Online
          JonB
          wrote on last edited by JonB
          #5

          @efremdan1 said in transmitting extra data with signals that already sends extra data:

          Doesn't work. Upon clicking, Python sends the error:
          TypeError: <lambda>() missing 1 required positional argument: 'checked'

          Oh, but it does work! You asked for

          I want to use the clicked signal instead of the pressed signal.

          However, only clicked signal/slot takes that checked parameter, not pressed signal. And you have left in your original button.pressed.connect(lambda ...) instead of changing to button.clicked.connect(lambda ...), haven't you? :)

          1 Reply Last reply
          0
          • E Offline
            E Offline
            efremdan1
            wrote on last edited by
            #6

            No I didn't. Here's the code that doesn't work:

            from PySide2.QtWidgets import (
                QApplication, QMainWindow, QAction, QPushButton,
                QWidget, QLabel, QVBoxLayout, QHBoxLayout
            )
            
            import sys
            
            
            class Window(QWidget):
            
                def __init__(self):
                    super().__init__()
            
                    v = QVBoxLayout()
                    h = QHBoxLayout()
            
                    for a in range(10):
                        button = QPushButton(str(a))
                        button.clicked.connect(
                            lambda checked, val=a: self.button_pressed(val)
                        )
                        h.addWidget(button)
            
                    v.addLayout(h)
                    self.label = QLabel("")
                    v.addWidget(self.label)
                    self.setLayout(v)
            
                def button_pressed(self, n):
                    self.label.setText(str(n))
            
            
            app = QApplication(sys.argv)
            w = Window()
            w.show()
            app.exec_()
            

            That works for you? I'm using Python 3.9.0 and PySide2 5.15.2. What versions are you using that has that working?

            JonBJ 4 Replies Last reply
            0
            • E efremdan1

              No I didn't. Here's the code that doesn't work:

              from PySide2.QtWidgets import (
                  QApplication, QMainWindow, QAction, QPushButton,
                  QWidget, QLabel, QVBoxLayout, QHBoxLayout
              )
              
              import sys
              
              
              class Window(QWidget):
              
                  def __init__(self):
                      super().__init__()
              
                      v = QVBoxLayout()
                      h = QHBoxLayout()
              
                      for a in range(10):
                          button = QPushButton(str(a))
                          button.clicked.connect(
                              lambda checked, val=a: self.button_pressed(val)
                          )
                          h.addWidget(button)
              
                      v.addLayout(h)
                      self.label = QLabel("")
                      v.addWidget(self.label)
                      self.setLayout(v)
              
                  def button_pressed(self, n):
                      self.label.setText(str(n))
              
              
              app = QApplication(sys.argv)
              w = Window()
              w.show()
              app.exec_()
              

              That works for you? I'm using Python 3.9.0 and PySide2 5.15.2. What versions are you using that has that working?

              JonBJ Online
              JonBJ Online
              JonB
              wrote on last edited by
              #7

              @efremdan1
              Mine almost same, yep. I can't spot what/if you have different. Mine works :) If I deliberately change to button.pressed.connect I get just your error, so seemed likely you'd still be there too!

              I'll have to look tomorrow. I'll try copy/paste your code. Remind me :)

              1 Reply Last reply
              0
              • E efremdan1

                No I didn't. Here's the code that doesn't work:

                from PySide2.QtWidgets import (
                    QApplication, QMainWindow, QAction, QPushButton,
                    QWidget, QLabel, QVBoxLayout, QHBoxLayout
                )
                
                import sys
                
                
                class Window(QWidget):
                
                    def __init__(self):
                        super().__init__()
                
                        v = QVBoxLayout()
                        h = QHBoxLayout()
                
                        for a in range(10):
                            button = QPushButton(str(a))
                            button.clicked.connect(
                                lambda checked, val=a: self.button_pressed(val)
                            )
                            h.addWidget(button)
                
                        v.addLayout(h)
                        self.label = QLabel("")
                        v.addWidget(self.label)
                        self.setLayout(v)
                
                    def button_pressed(self, n):
                        self.label.setText(str(n))
                
                
                app = QApplication(sys.argv)
                w = Window()
                w.show()
                app.exec_()
                

                That works for you? I'm using Python 3.9.0 and PySide2 5.15.2. What versions are you using that has that working?

                JonBJ Online
                JonBJ Online
                JonB
                wrote on last edited by JonB
                #8

                @efremdan1
                Hi.

                First I checked yours and agree it does give the TypeError: <lambda>() missing 1 required positional argument: 'checked' error message.

                Then I looked a lot mine, frowning to see the difference, because mine does work, and only correctly gives just that error if I connect to, say, pressed signal which does not have the checked argument. Until I suddenly noticed that mine had # from PySide2 import ... commented out and from PyQt5 import ... instead, using trusty & robust PyQt5.... (And I also tested yours with PyQt5 instead and, surprise, surprise, yours then works too.)

                So now what we have is a difference in behaviour for PySide2 compared against PyQt5 :( I will now have a look around/play to see if I can see what we are supposed to do in PySide2 for this which actually works....

                1 Reply Last reply
                0
                • E efremdan1

                  No I didn't. Here's the code that doesn't work:

                  from PySide2.QtWidgets import (
                      QApplication, QMainWindow, QAction, QPushButton,
                      QWidget, QLabel, QVBoxLayout, QHBoxLayout
                  )
                  
                  import sys
                  
                  
                  class Window(QWidget):
                  
                      def __init__(self):
                          super().__init__()
                  
                          v = QVBoxLayout()
                          h = QHBoxLayout()
                  
                          for a in range(10):
                              button = QPushButton(str(a))
                              button.clicked.connect(
                                  lambda checked, val=a: self.button_pressed(val)
                              )
                              h.addWidget(button)
                  
                          v.addLayout(h)
                          self.label = QLabel("")
                          v.addWidget(self.label)
                          self.setLayout(v)
                  
                      def button_pressed(self, n):
                          self.label.setText(str(n))
                  
                  
                  app = QApplication(sys.argv)
                  w = Window()
                  w.show()
                  app.exec_()
                  

                  That works for you? I'm using Python 3.9.0 and PySide2 5.15.2. What versions are you using that has that working?

                  JonBJ Online
                  JonBJ Online
                  JonB
                  wrote on last edited by JonB
                  #9

                  @efremdan1
                  OK, so my first attempt to "correct" this works, under PySide2 as well as PyQt5:

                  lambda rubbish=0, val=a: self.button_pressed(val)
                  

                  Note that I have assigned a value to the first parameter. I picked rubbish to show there is no connection between that variable name and the checked name in the signal. Obviously for clarity you should really write checked=False.

                  Now, while this works and is hopefully good enough for you(?), I don't think it is "right". The signal has a parameter

                  PySide2.QtWidgets.QAbstractButton.clicked([checked=false])
                  

                  and we should be able to get the passed-in value of checked in our lambda so that we can pass that on correctly. I don't see that we can. Note that attempting

                  lambda checked=checked, val=a: self.button_pressed(val)
                  

                  gives NameError: name 'checked' is not defined in both, so that's not right.

                  I haven't even verified whether the PyQt5 lambda checked, val=a: self.button_pressed(val) really gets a passed-in value for checked, and I can write rubbish there too and it works. So the only difference I see is that PyQt5 allows this first positional parameter without any =default but PySide2 demands a default.

                  I think I'll leave this to you at this point. The code I've given you is probably enough for you because you don't bother looking at the checked value....

                  1 Reply Last reply
                  1
                  • E efremdan1

                    No I didn't. Here's the code that doesn't work:

                    from PySide2.QtWidgets import (
                        QApplication, QMainWindow, QAction, QPushButton,
                        QWidget, QLabel, QVBoxLayout, QHBoxLayout
                    )
                    
                    import sys
                    
                    
                    class Window(QWidget):
                    
                        def __init__(self):
                            super().__init__()
                    
                            v = QVBoxLayout()
                            h = QHBoxLayout()
                    
                            for a in range(10):
                                button = QPushButton(str(a))
                                button.clicked.connect(
                                    lambda checked, val=a: self.button_pressed(val)
                                )
                                h.addWidget(button)
                    
                            v.addLayout(h)
                            self.label = QLabel("")
                            v.addWidget(self.label)
                            self.setLayout(v)
                    
                        def button_pressed(self, n):
                            self.label.setText(str(n))
                    
                    
                    app = QApplication(sys.argv)
                    w = Window()
                    w.show()
                    app.exec_()
                    

                    That works for you? I'm using Python 3.9.0 and PySide2 5.15.2. What versions are you using that has that working?

                    JonBJ Online
                    JonBJ Online
                    JonB
                    wrote on last edited by JonB
                    #10

                    @efremdan1
                    I can never leave something unsatisfactory alone... :(

                    BTW, the accepted solution at https://stackoverflow.com/questions/38090345/pass-extra-arguments-to-pyqt-slot-without-losing-default-signal-arguments is worth reading. Using PyQt5 at least, you can see he shares my understanding of how we should add checked as first parameter. Note that functools.partial is another way which might work right in PySide2.

                    I have just found exactly your issue reported from PySide2: https://forum.learnpyqt.com/t/getting-typeerror-lambda-missing-1-required-positional-argument-checked/586

                    button.clicked.connect(lambda checked, a=a: self.button_clicked(a))
                    TypeError: () missing 1 required positional argument: ‘checked’.
                    I use PySide2 and Python 3.8

                    The response there of needing to use QSignalMapper is irrelevant, but see again https://stackoverflow.com/a/52283813/489865

                    I am not finding a satisfactory answer for the behaviour exhibited in PySide2, and at the moment see it as a bug.

                    1 Reply Last reply
                    1
                    • E Offline
                      E Offline
                      efremdan1
                      wrote on last edited by
                      #11

                      Thank you so much @JonB! You're correct that the solution you gave works satisfactorily for me.

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved