What does @pyqtSlot() do?

Is the pyqtSlot decorator even necessary?
Heads up! You've already completed this tutorial.

When working with Qt slots and signals in PyQt6 you will discover the @pyqtSlot decorator. This decorator is used to mark a Python function or method as a slot to which a Qt signal can be connected. However, as you can see in our signals and slots tutorials you don't have to use this. Any Python function or method can be used, normally, as a slot for a Qt signals. But elsewhere, in our threading tutorials we do use it.

What's going on here?

  • Why do you sometimes use @pyqtSlot but usually not?
  • What happens when you omit it?
  • Are there times when it is required?

What does the documentation say?

The PyQt6 documentation has a good explanation:

Although PyQt6 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt6 provides the pyqtSlot() function decorator to do this.

Connecting a signal to a decorated Python method has the advantage of reducing the amount of memory used and is slightly faster.

From the above we see that:

  • Any Python callable can be used as a slot when connecting signals.
  • It is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it.
  • There is a side-benefit in that marking a function or method with pyqtSlot() reduces the amount of memory used, and makes the slot faster.

When is it necessary?

Sometimes necessary is a bit vague. In practice the only situation where you need to use pyqtSlot decorators is when working with threads. This is because of a difference in how signal connections are handled in decorated vs. undecorated slots.

Over 10,000 developers have bought Create GUI Applications with Python & Qt!
Create GUI Applications with Python & Qt6
Take a look

Downloadable ebook (PDF, ePub) & Complete Source code

Also available from Leanpub and Amazon Paperback

[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

  1. If you decorate a method with @pyqtSlot then that slot is created as a native Qt slot, and behaves identically
  2. If you don't decorate the method then PyQt6 will create a "proxy" object wrapper which provides a native slot to Qt

In normal use this is fine, aside from the performance impact (see below). But when working with threads, there is a complication: is the proxy object created on the GUI thread or on the runner thread. If it ends up on the wrong thread, this can lead to segmentation faults. Using the pyqtSlot decorator side-steps this issue, because no proxy is created.

When updating my PyQt6 book I wondered -- is this still necessary?! -- and tested removing it from the examples. Many examples continue to work, but some failed. To be safe, use pyqtSlot decorators on your QRunnable.run methods.

What about performance?

The PyQt6 documentation notes that using native slots "has the advantage of reducing the amount of memory used and is slightly faster". But how much faster is it really, and does decorating slots actually save much memory?

We can test this directly by using this script from Oliver L Schoenborn. Updating for PyQt6 (replace PyQt5 with PyQt6 and it will work as-is) and running this we get the following results:

See the original results for PyQt5 for comparison.

First the results for the speed of emitting signals when connected to a decorated slot, vs non-decorated.

python
Raw slot mean, stddev:  0.578 0.024
Pyqt slot mean, stddev: 0.587 0.021
Percent gain with pyqtSlot: -2 %

The result shows pyqtSlot as 2% slower, but this is negligible (the original data on PyQt5 also showed no difference). So, using pyqtSlot will have no noticeable impact on the speed of signal handling in your applications.

Next are the results for establishing connections. This shows the speed, and memory usage of connecting to decorated vs. non-decorated slots.

PyQt/PySide 1:1 Coaching with Martin Fitzpatrick — Get one on one help with your Python GUI projects. Working together with you I'll identify issues and suggest fixes, from bugs and usability to architecture and maintainability.

Book Now 60 mins ($195)

python
Comparing mem and time required to create 10000000 connections, 1000 times

Measuring for 1000000 connections
              # connects     mem (bytes)          time (sec)
Raw         :   1000000      949186560 (905MB)    9.02
Pyqt Slot   :   1000000       48500736 ( 46MB)    1.52
Ratios      :                       20               6

The results show that decorated slots are about 6x faster to connect to. This sounds like a big difference, but it would only be noticeable if an application was connecting a considerable number of signals. Based on these numbers, if you connected 100 signals the total execution time difference would be 0.9 ms vs 0.15 ms. This is negligible, not to mention imperceptible.

Perhaps more significant is that using raw connections uses 20x the memory of decorated connections. Again though, bear in mind that for a more realistic upper limit of connections (100) the actual difference here is 0.09MB vs 0.004MB.

The bottom line: don't expect any dramatic improvements in performance or memory usage from using slot decorators, unless you're working with insanely large numbers of signals or making regular connections you won't see any difference at all. That said, decorating your slots is an easy win if you need it.

Are there any other reasons to decorate a slot?

In Qt signals can be used to transmit more than one type of data by overloading signals and slots with different types.

For example, with the following code the my_slot_fn will only receive signals which match the signature of two int values.

python
@pyqtSlot(int, int)
def my_slot_fn(a, b):
    pass

This is a legacy of Qt5 and not recommended in new code. In Qt6 all of these signals have been replaced with separate signals with distinct names for different types. I recommend you follow the same approach in your own code for the sake of simplicity.

Conclusion

The pyqtSlot decorator can be used to mark Python functions or methods as Qt slots. This decorator is only required on slots which may be connected to across threads, for example the run method of QRunnable objects. For all other slots it can be omitted. There is a very small performance benefit to using it, which you may want to consider when your application makes a large number of signal/slot connections.

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

What does @pyqtSlot() do? was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.