This repository has been archived by the owner on Dec 27, 2021. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
92 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
Title: PySide2 non-migration | ||
Date: 2019-09-22 | ||
|
||
I was about to finish another sprint (implementing a saner copy&paste system, as I [hinted | ||
earlier](/blog/2019/09-08-development-update.md)). So I ran `mypy` over the sources and saw that it | ||
complains about a bunch of things, which are caused by an issue with `PyQt5`. | ||
|
||
`PyQt5` seems to not like classes, which explicitly inherit from multiple "`Q`" classes. | ||
E.g. something like that: | ||
|
||
```python | ||
class SomeMixin(QtCore.QWidget): | ||
def someMethod(self): | ||
self.update() | ||
|
||
class SomeLineEdit(SomeMixin, QtCore.QLineEdit): | ||
... | ||
|
||
``` | ||
|
||
`QLineEdit` is a subclass of `QWidget`, so the above should be perfectly fine. But not for | ||
`PyQt5`. When I tried to make a minimal example, it just segfaulted, but I vaguely remember seeing | ||
some exception being raised. To workaround that, I had to make such mixin classes not inherit from a | ||
"`Q`" class (e.g. just `object`), which is perfectly fine at runtime. But `mypy` has no idea that | ||
this mixin is only used with some kind of `QWidget` and that `self.update()` is a valid method. So I | ||
have to make `mypy` suppress all those false-positive warnings, which makes the code look ugly, and | ||
I lose type checking for any real issues. | ||
|
||
Besides that `PyQt5`'s support for type annotations was not so great anyway. I'm maintaining my own | ||
set of stubs for it, which are based on the original `PyQt5` stubs, but with lots of manual tweaks | ||
to make them actually useful. | ||
|
||
So when I saw that `Qt` now [officially includes Python | ||
bindings](http://blog.qt.io/blog/2018/12/06/qt-5-12-lts-released/) in the shape of | ||
[`PySide2`](https://wiki.qt.io/Qt_for_Python), I was interested in evaluating a migration. There was | ||
an open [ticket for adding stubs](https://bugreports.qt.io/browse/PYSIDE-735), but that was fixed | ||
for version 5.13. | ||
|
||
It's easy to install via `pip`, so I made a quick test to see if `PySide2` was also suffering from | ||
the inheritance issue above. It wasn't, so let's give a real migration a try. | ||
|
||
`PySide2` looks sufficiently similar to `PyQt5`, that you could think a simple `s/PyQt5/PySide2/` | ||
might already be enough. For some projects it might already be that, plus some more trivial renames | ||
like `pyqtSignal`→`Signal` or `pyqtProperty`→`Property`. | ||
|
||
But there are more subtle differences, which make it much harder (at least for noisicaä)... | ||
|
||
* The `connect()` method of signals returns a boolean. Apparently it can fail (no idea under which | ||
conditions...), so you should probably check the return value. Which is very unpythonic - it | ||
should just raise an exception. And in `PyQt5` it returns a `Connection` instance, which can be | ||
passed to `disconnect()`. That's the only way to disconnect a `lambda` function (without carrying | ||
a reference to that function around), which I do a lot, so that's annoying. | ||
* The way how a class level `Signal` attribute gets turned into an instance level `SignalInstance` | ||
attribute looks odd. In `PyQt5` the `pyqtSignal` implements the property protocol, so accessing | ||
the attribute on an instance returns the appropriate `pyqtBoundSignal` instance. In `PySide2` the | ||
metaclass does somehow create a `SignalInstance` for each `Signal` and [injects that into the | ||
instance's | ||
`__dict__`](https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside2/libpyside/pyside.cpp?h=5.13.1#n313), | ||
though I haven't really figured out when this actually happens. The problem for me is that there | ||
doesn't seem to be a way to get from an `Signal` instance and the owning object to the | ||
`SignalInstance`. Which I do in some helper function, which saves me a lot of boilerplate code. I | ||
could find a workaround, but that is really ugly... Parsing the `str()` of the `Signal` | ||
instance... I won't say more. Too embarrassing. | ||
* Signals cannot use [an `Enum` as the type](https://bugreports.qt.io/browse/PYSIDE-239). That bug | ||
is already 5 years old and for an ancient version of `PySide`. The workaround is to declare those | ||
signals with type `object` (and lose some type safety). | ||
* `QSettings.value()` does not return the default value, if it is `0` or `False`. That seems like a | ||
plain and simple bug. | ||
|
||
Those are the issues, which I have found so far. At least the unittests are now passing, but that | ||
doesn't really mean that much, because the test coverage for the UI code isn't that great. And | ||
getting there wasn't easy, because `PySide2` is also very crash happy. So instead of a nice Python | ||
exception telling me where and what was wrong, I just got the unhelpful "Segmentation fault (core | ||
dumped)" message. I had to perform the "install from source" dance to get a version with debug | ||
symbols, so `gdb` could at least tell me something about the problem. | ||
|
||
And now I'm getting this exception: | ||
|
||
```text | ||
Traceback (most recent call last): | ||
[...] | ||
File "/home/pink/noisicaa/build/noisicaa/ui/control_value_connector.py", line 62, in __init__ | ||
self.valueChanged.connect(self.__onValueEdited) | ||
TypeError: connect() takes 3 positional arguments but 4 were given | ||
``` | ||
|
||
Sorry, but that simply does not make sense. | ||
|
||
It would have been nice to have a viable alternative to `PyQt5`, and perhaps `PySide2` is that, if | ||
you're starting a new project from scratch. But migrating noisicaä does not seem worth the effort, | ||
at least not now. There is at least some hope that development of `PySide2` gets a boost, at least | ||
for a while, now that it has been included in the `Qt` canon. Let's give it some more time. |