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

transmitting extra data with signals that already sends extra data

Scheduled Pinned Locked Moved Solved Qt for Python
11 Posts 2 Posters 2.9k Views
  • 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 Offline
    E Offline
    efremdan1
    wrote on last edited by
    #1

    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 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 Offline
      JonBJ Offline
      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

        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 Offline
        JonBJ Offline
        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 Offline
            JonBJ Offline
            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 Offline
              JonBJ Offline
              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 Offline
                  JonBJ Offline
                  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 Offline
                    JonBJ Offline
                    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 Offline
                      JonBJ Offline
                      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 Offline
                        JonBJ Offline
                        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