This is about choosing among, and converting between, two alternative bindings of Python to Qt:
- PyQt: open source but commercial and GPL but not LGPL
- PySide: open source and non-commercial and LGPL
The PySide project also tells about some of the technical differences. This post is a little more pragmatic, and discusses non-technical issues: licensing and politics. This post is mostly about converting from PySide to PyQt, but might be useful for the other direction.
This post may be wrong, and is certainly incomplete.
What the different licensings mean
Licensing is complicated, this is just a summary:
You can use PyQt (since it is GPL):
- for free as long as your app is also GPL (you must reveal your source code.)
- for a fee if your app is proprietary (you don’t reveal your source code)
There are other stipulations, such as displaying copyrights, and whether your app reveals PyQt to your users (lets your users script using PyQt.)
You can use PySide (since it is LGPL):
- for free as long as you allow users to recombine the object code of your app with an altered PySide library. (See what recombine means for Python and Pyinstaller apps.)
Shortcomings of PySide
I have experienced these flaws:
- PySide Qt enums are weird and don’t pickle
- PyInstaller support for PySide is weak (it doesn’t work on some platforms (e.g. OSX and Windows) for some features (e.g. Qt plugins such as for jpeg image formats.))
- PySide lags Qt development (as of this writing PySide supports Qt4 and not Qt5, whereas PyQt already supports Qt5)
- PySide support is spotty (since it is voluntary, and since bindings are complicated, high tech.)
The owners of PyQt (Riverbank Computing PLC) have an effective business strategy: by releasing their code GPL, they have gained as users all the open source authors (since PyQt works well and is well supported.) Only the users who are not open source but don’t want to pay for tools (or want to expose scripting) are forced to the PySide camp , and that is a small number of users. This strategy is similar to the Red Hat strategy.
The situation is different from say, an open source compiler gcc. Both compilers and bindings are high tech. But the pool of users of a Python Qt binding is small.
There is a long history, and ongoing turmoil, surrounding the situation of having alternative, open source Python Qt bindings.
No matter what your politics on the matter of open source software, you must respect the opinions and rights of others. If you decide to use PyQt, you should meet the stipulations of their license. They are providing a valuable service, if you want that service and are obligated to pay their fee (your app is NOT GPL), please pay.
Summary of process for converting from PySide to PyQt
It is easy, but not trivial.
It can be reversed, but you probably don’t want to do it in a reversible way (don’t monkey patch the code with ‘if PYSIDE’ statements.)
The conversions can be categorized:
- signatures: where PyQt uses a different name for keyword parameters
- type-checking : where PyQt does more strict type checking
- Qt enum
- a small preamble to set up PyQt API version
- import PySide.* -> import PyQt4.* (a few global search/replace regexes will reliably do this, back and forth.)
- from …. import Signal -> from … import pyqtSignal as Signal (and similarly for Slot)
- PyQt raises exception on failed connect() whereas PySide returns a sucess/failure result
Qt enum differences
The more general issue “what is an enum type” is contentious. Does defining an enum define a new type? Are instances of that type compatible with type “Int”?
This code illustrates the differences:
qtEnum = Qt # PyQt qtEnum = Qt.BrushStyle # PySide foo = qtEnum.SolidPattern # an instance of the enum
In PySide, a Qt enum is represented by a class and enum instances are class attributes. They don’t pickle (because pickle does not do classes.)
(I can’t yet discuss Qt enums in PyQt.)
These are the signature differences I found in execution (so it is not a complete list):
- QStatusBar.showMessage(…, timeout -> msecs
- QFileDialog.getOpenFilename() has different signature
PyQt does more strict type checking. When converting from PyQt to PySide, PyQt will complain more. I think this is useful, often pointing out potential bugs, or portability problems, in my code.
In general, both C++ and Python do strict type checking, but C++ does it statically (at compile time) and Python does it dynamically (at run-time).
Example: this code seems to work in PySide, but PyQt complains:
QKeySequence(event.key() | event.modifiers())
Example: PyQt gives errors for keyword parameters that don’t match a keyword, whereas PySide lets it slide:
QDialog.exec_( foo, atAction=bar) # the keyword should be "at"
I expected PyQt and PySide’s treatment of signals to be portable, but found a few problems:
PyQt seems a little more strict about type checking parameters to signals, (which I think is a good thing.) (TODO: example.)
In one case, I needed to restructure multiple inheritance of a mixin class, to get a PyQt signal to connect. (TODO: example.)
PySide accepts: @Slot(QPrinter) while PyQt wants @Slot(‘QPrinter’). That is, all c++ types (that is, Qt types, contrasted with Python types, should be quoted strings for PyQt, when used as a parameter to pyqtSlot.
Other strategies for converting imports
PyQt supports two versionsof its API. The two versions come from a history of advancement: the new version is better than the old. If you have and support older versions of your app, you might need to be concerned about the two versions. There are ways to support both versions of the PyQt API as well as the PySide API. In other words, you can make your code agnostic of the binding brand (PyQt versus PySide) and version of the binding. See Supporting both APIs.