66

I'm having issues with a custom signal in a class I made.

Relevant code:

self.parse_triggered = QtCore.pyqtSignal()

def parseFile(self):
    self.emit(self.parse_triggered)

Both of those belong to the class: RefreshWidget. In its parent class I have:

self.refreshWidget.parse_triggered.connect(self.tabWidget.giveTabsData())

When I try to run the program, I get the error:

AttributeError: 'PyQt4.QtCore.pyqtSignal' object has no attribute 'connect'

Help? Thanks in advance.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Dane Larsen
  • 1,534
  • 2
  • 18
  • 27
  • 1
    `self.tabWidget.giveTabsData()` looks suspect as it would need to return a function / handler, but that shouldn't be related to the actual error you're getting. Otherwise, it looks good. – Kaleb Pederson Jun 04 '10 at 00:26
  • 1
    I'm having the exact same problem. If you find a resolution, could you post and edit with what the problem was? – Joel Verhagen Jun 04 '10 at 03:40

6 Answers6

132

I had the same exact problem as you.

Try moving

self.parse_triggered = QtCore.pyqtSignal()

out of your constructor but inside your class declaration. So instead of it looking like this:

class Worker(QtCore.QThread):
    def __init__(self, parent = None):
        super(Worker, self).__init__(parent)

        self.parse_triggered = QtCore.pyqtSignal()

It should look like this:

class Worker(QtCore.QThread):
    parse_triggered = QtCore.pyqtSignal()

    def __init__(self, parent = None):
        super(Worker, self).__init__(parent)

This might not be at all what you are looking for, but it worked for me. I switched back to old-style signals anyways because I haven't found a way in new-style signals to have an undefined number or type of parameters.

embert
  • 7,336
  • 10
  • 49
  • 78
Joel Verhagen
  • 5,110
  • 4
  • 38
  • 47
  • 53
    Can someone explain why this is? – Xiong Chiamiov Jun 18 '10 at 05:00
  • 11
    This has a problem. The signal will be shared between each instance. – Ken Kinder Feb 18 '13 at 01:41
  • 19
    The reason that this works is due to the way the PyQt library has internally implemented `pyqtSignal`. Specifically, under [Support for Signals and Slots](http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html) we find that the library distinguishes between unbound and bound signals. The magic is performed as follows: "A signal (specifically an unbound signal) is an attribute of a class that is a sub-class of `QObject`. When a signal is referenced as an attribute of an instance of the class then PyQt5 automatically binds the instance to the signal in order to create a bound signal." – barik Nov 06 '13 at 23:44
  • One more note, the superclass must be initialised as well. I was missing that call in the subclass __init__() function assuming it would be done automatically and that caused the same error message regarding signals. – Johan Dec 10 '17 at 14:06
  • Is there a way to get around this, if the class was already defined through Qt Creator, heavily layouted and dynamically translated with pyuic, or do I have to subclass it myself manually to enable to signal like `parse_triggered`? – komodovaran_ Jul 30 '18 at 19:12
  • More info on this can be found here: https://stackoverflow.com/questions/48425316/how-to-create-pyqt-properties-dynamically – Joe Dec 11 '19 at 09:25
  • 2
    [This](https://stackoverflow.com/a/15033578/7919597) answer explains why the signal is not shared between instances as @ken-kinder stated above: the class attribute is of type `` while the instance attribute is of type `` – Joe Dec 11 '19 at 09:26
  • It's important to realize here that you do NOT use the class variable signal to connect a signal listener (this signal is unbound), but rather the instance signal variable of the class which PyQt creates for you under the hood. – tobi delbruck Jan 28 '23 at 10:48
78

You also get that error message if you fail to call super() or QObject.__init__() in your custom class.

A checklist for defining custom signals in a class in Qt in Python:

  • your class derives from QObject (directly or indirectly)
  • your class __init__ calls super() (or calls QObject.__init__() directly.)
  • your signal is defined as a class variable, not an instance variable
  • the signature (formal arguments) of your signal matches the signature of any slot that you will connect to the signal e.g. () or (int) or (str) or ((int,), (str,))
Cody Piersall
  • 8,312
  • 2
  • 43
  • 57
bootchk
  • 1,926
  • 17
  • 14
  • The error I got here was along the lines of "signals must be bound to QObject instances, not ". Since I had derived from QObject, I found this baffling. But I hadn't called the QObject `__init__` in any way. Thanks for the tip! – Stephan A. Terre Dec 04 '14 at 20:22
  • 1
    Since I was using a QRunnable, I also had this problem since it does not inherit QObject. The solution was to make my class a subclass of QObject and also QRunnable, and call both `QObject.__init__(self)` and `QRunnable.__init__(self)` in my class' `__init__` method. – aktungmak Jan 05 '15 at 14:11
  • This is extremely important to remember! Thanks! – Amibest Jul 31 '22 at 15:55
10

I have recently started working with PySide (Nokia's own version of PyQt), and saw the exact same behaviour (and solution) with custom new-style signals. My biggest concern with the solution was that using a class variable to hold the signal would mess things up when I have multiple instances of that class (QThreads in my case).

From what I could see, QtCore.QObject.__init__(self) finds the Signal variable in the class and creates a copy of that Signal for the instance. I have no idea what QObject.__init__() does, but the resulting Signal does proper connect(), disconnect() and emit() methods (and also a __getitem__() method), whereas the class Signal or standalone Signal variables created outside of a QObject-derived class do not have these methods and can't be used properly.

Jare
  • 101
  • 1
  • 2
6

To use the signal/slot system you need to have a QObject inherited class.

Here is a simple example:



    from PySide import QtCore
    class LivingBeing(QtCore.QObject):
      bornSignal = QtCore.Signal() # initialise our signal

      def __init__(self,name):
        QtCore.QObject.__init__(self) # initialisation required for object inheritance
        self.bornSignal.connect(self.helloWorld) # connect the born signal to the helloworld function
        self.name = name #
        self.alive = False

      def summonFromClay(self):
        self.alive = True
        self.bornSignal.emit() # emit the signal

      def helloWorld(self):
         print "Hello World !, my name is %s, this place is so great !" % self.name

    # now try the little piece of code
    if __name__ == '__main__':
      firstHuman = LivingBeing('Adam')
      firstHuman.summonFromClay()

 
Lokinou
  • 1,039
  • 10
  • 13
6

I had the same problem. I forgot that if a class uses Signals, then it must inherit from QObject. I was doing some re-factoring and did not pay attention to this.

FrancoLM
  • 61
  • 1
  • 1
0

Why do you connect directly to the signal, while you can do self.connect(widget, SIGNAL('parse_triggered()'), listener.listening_method)?

where self is, for example, the form itself, and may be the same as listener

Guard
  • 6,816
  • 4
  • 38
  • 58
  • Supposedly that is the "old way" of doing connect(). From what I've read, that method won't put up any errors even if they exist. – Dane Larsen Jun 04 '10 at 14:58
  • 2
    This is old-style signals and slots. Both work, but people tend to think the new-style is more elegant/pythonic. – Joel Verhagen Jun 04 '10 at 20:22