The question of Algis Kabaila prompted me to try to simplify the use of QApplication, which is more complex than it really needs to be. This is probably not worth a formal PSEP as it is fairly simple, but I'm submitting here so that everyone can comment.
Cheers,
Farsmo
-------------------------------------------------------------------------
(copy of bug 607)
QApplication.exec_() is designed around C++: it's convenient to have exec_()
return a value instead of calling exit() because it makes additional cleanup
much easier.
But in Python, sys.exit() only raises an exception, which can be intercepted if
needed. So there is no reason not to automatically raise an exception. In order
to both fix the ugly exec_() and maintain compatibility with programs that
don't expect exec_() to raise an exception, I propose to simply define
def execute(self):
sys.exit(self.exec_())
or, perhaps it would be wiser to define:
def execute(self):
code = self.exec_()
if code!=0:
sys.exit(code)
so that execution does not stop if exec_() is successful.
Likewise, I don't think there is any reason for QApplication to demand passing
sys.argv: unlike in C++, it doesn't have to be manually passed, so
QApplication() could be QApplication(sys.argv).
So the typical program prologue would change from
if __name__=="__main__":
import sys
app = QApplication(sys.argv)
...
sys.exit(app.exec_())
to
if __name__=="__main__":
app = QApplication()
...
app.execute()
The icing on the cake would be to use the with statement:
if __name__=="__main__":
with QApplication():
...
where QApplication's __exit__ would simply be execute().
_______________________________________________
PySide mailing list
PyS...@lists.openbossa.org
http://lists.openbossa.org/listinfo/pyside
And I'm replying to myself to add that there are other possibilities too:
# using a callback
def main():
...
if __name__=="__main__":
QApplication().execute(main) # or QApplication.execute(main) for short
# using a decorator
@QMainFunction
def main():
...
if __name__=="__main__":
main()
The decorator solution looks pretty satisfactory to me, but it requires either defining a new class like QMainFunction, or adding a static method decorator such as @QApplication.main.
Cheers,
Farsmo
I'd favor forms that allow you to keep a reference to the app object
with the minimum fuss and magic, because for some apps you might need
it... the forms in your first e-mail, to be exact.
Also, the decorator approach is very Python, but to me gives the
impression that there are two main "functions" now, because I've
always though of the "if __name__=='__main__'" bit as being the
semantic equivalent of the C/C++ main().
--
Ilkka
--
Ilkka
github.com/ilkka
twitter.com/ilkkalaukkanen
2011/1/14 Ilkka Laukkanen <ilkka.s....@gmail.com>:
> Also, the decorator approach is very Python, but to me gives the
> impression that there are two main "functions" now, because I've
> always though of the "if __name__=='__main__'" bit as being the
> semantic equivalent of the C/C++ main().
With the exeception that you don't create a new scope, so everything
you have in that block (the True branch of that "if") will bleed out
into global scope. Not so if you create a main() function. The reason
for "if __name__ == '__main__'" is to allow your script to be imported
as module without running any code (that's why for example Python's
"zipfile" module works as both a library and as a command-line utility
when used as "python -m zipfile"). So it still does make sense to have
both the "if __name__" part to avoid running some code when being
imported as a module and a separate "main()" function to avoid the
local variables in the main function bleed out into global scope.
As for the original topic (shortening the QApplication propogue) - it
does not really get that much more compact (in terms of lines saved
vs. complexity added), and it would make it more difficult (i.e. not
straightforward) to "translate" a PySide app to the C++ API of Qt. You
could still write a "convenience" module yourself and share it with
the list / put it in the PySide Wiki and use it in all your projects -
in my opinion, it does not belong in the standard PySide distribution
(but maybe there's a place for a "PySide contrib" section?).
HTH.
Thomas
On the other hand, due to the dynamic nature of Python's type system, QApplication.instance() will return the current QApplication. This is not true in C++ as ::instance() only returns a QCoreApplication*, although I'm not sure what the rationale for not providing QApplication::instance() is.
What does the PySide team think about using QApplication.instance() in this way? Is it discouraged?
Farsmo
Like I said to linjunhalida, it is more about unifying QCoreApplication.exit() and sys.exit() in order to avoid bugs and simplify the programming model than it is about shortening the code.
Actually, you have to think in terms of incentives to really understand what I'm proposing. As I explained in reply to linjunhalida, the C++ startup idiom is a great way to provide incentives to pass the real (argc,argv) to Qt, and to return the exec() value to the OS. Unfortunately when you translate this idiom to Python verbatim, the incentives shift: because sys is not available by default and because it's easy to build an empty list from scratch, it is very tempting to pass [] instead of sys.argv; and because the typical Python program doesn't end with sys.exit, it is very tempting to ignore the value of exec_(). To fix that, providing a headache-free way of doing the right thing will always be the best incentive.
As for trying to deviate as little as possible from the C++ API, that could be a valid goal for the project, but I think PySide developers are not aiming for that: they try to improve the Python API wherever it can be improved, as can be seen with the new signals and slots and the Python-style attributes PSEP. Both make porting to C++ harder, and the lack of distinction between pointers and references was already making porting non-trivial. Of course simplifying QApplication would not be as big of an improvement as these changes, but then again it would not make porting to C++ much harder (if you have trouble looking up the C++ Qt startup idiom, you will have a very hard time porting a whole application to C++).
Cheers,
Farsmo
In C++ QCoreApplication::instance returns a QCoreApplication pointer and
QApplication::instance returns a QApplication pointer.
In Python both return the correct type because the bindings always try to
discover the real type of a C++ variable, i.e.
app = QApplication([])
type(app) == type(QCoreApplication.instance()) # True
>
> Farsmo
>
> _______________________________________________
> PySide mailing list
> PyS...@lists.openbossa.org
> http://lists.openbossa.org/listinfo/pyside
--
Hugo Parente Lima
INdT - Instituto Nokia de Tecnologia
No, unless it was fixed recently: it's not in http://doc.qt.nokia.com/4.7/qapplication.html and
QApplication::instance()->styleSheet()
fails.
Maybe this is just an oversight and not a deliberate decision?
Cheers,
Yeah.. you are right, there's no QApplication::instance(), I thought there was
one, anyway QCoreApplication.instance() in Python will return a QApplication
if it is a QApplication.
and in C++ there's the qApp macro.
#define qApp (static_cast<QApplication *>(QCoreApplication::instance()))
> Maybe this is just an oversight and not a deliberate decision?
>
>
> Cheers,
> Farsmo
--
Oh, I had totally forgotten about that. So I guess QApplication.instance() is fine and passing the QApplication to main() is not necessary.
> As for trying to deviate as little as possible from the C++ API, that
> could be a valid goal for the project, but I think PySide developers
> are not aiming for that: they try to improve the Python API wherever
> it can be improved, as can be seen with the new signals and slots and
> the Python-style attributes PSEP. Both make porting to C++ harder,
> and the lack of distinction between pointers and references was
> already making porting non-trivial. Of course simplifying
> QApplication would not be as big of an improvement as these changes,
> but then again it would not make porting to C++ much harder (if you
> have trouble looking up the C++ Qt startup idiom, you will have a
> very hard time porting a whole application to C++).
Assuming you refer by "PySide developers" to me and the OpenBossa core
dev team, it'd be easier to find out our opinions on things just by
asking instead of making sweeping assumptions... :-)
I'm speaking mostly just for myself, but I think it's always a balancing
act between maintaining compatibility with C++ Qt and PyQt, and
improving the API. I believe compatiblity should have a very high
priority, but not at all costs (hence, renaming of pyqtSignal to Signal,
etc.). Any changes should be considered on a case-by-case basis.
Also, whenever the Python APIs are modified, keeping consistency
throughout the API would be the most important thing to consider. For
example, in some future backwards-incompatible PySide version, it might
be a good idea to make the classes throw an exception instead of
returning an error value, but that can't be implemented only partially.
In a similar manner, the Python properties could be adopted, but not for
just a few classes.
Due to the consistency concerns, but also because I'm worried about
maintainability, I'm also a bit unsure about enhancement bugs that
suggest minor tweaks to individual (or just a few) classes (e.g.
[1,2,3]). Not only that, but such changes result in limited benefit. At
the very least, I'd prefer getting some discussion here about the
policies regarding such changes, preferably leading to proper PSEPs.
Also, I'd like to emphasize the co-operative aspect of the whole
project: something leading to an accepted PSEP should not imply that the
core dev team promises to implement it, but that if someone provides an
implementation of sufficient quality, it will be accepted in the codebase.
But again, these are my opinions, and I'm known for changing them, given
convincing arguments. :-)
[1] http://bugs.openbossa.org/show_bug.cgi?id=606
[2] http://bugs.openbossa.org/show_bug.cgi?id=607
[3] http://bugs.openbossa.org/show_bug.cgi?id=615
Cheers,
ma.
My opinion about those enhancement suggestions:
> [1] http://bugs.openbossa.org/show_bug.cgi?id=606
Better than a toTuple method is to add the possibility to create tuples from
those objects, e.g.:
p = QPoint(1, 2)
t = tuple(p) # (1,2)
It's better because it doesn't add any "visible" method to the API, besides
being beauty IMO =]
> [2] http://bugs.openbossa.org/show_bug.cgi?id=607
It's something that you can use on your projects but not necessarily need to
be on PySide itself, IMO your attempt to simplify the code failed, not by your
bad, but because the original code is already too simple.
> [3] http://bugs.openbossa.org/show_bug.cgi?id=615
I agree with Matti, there's no need to add yet another incompatibility with
PyQt for basically no gain at all.
> Cheers,
>
> ma.
> _______________________________________________
> PySide mailing list
> PyS...@lists.openbossa.org
> http://lists.openbossa.org/listinfo/pyside
--
2011/1/17 Hugo Parente Lima <hugo...@openbossa.org>:
> On Monday 17 January 2011 13:07:35 Matti Airas wrote:
>> [1] http://bugs.openbossa.org/show_bug.cgi?id=606
>
> Better than a toTuple method is to add the possibility to create tuples from
> those objects, e.g.:
>
> p = QPoint(1, 2)
> t = tuple(p) # (1,2)
>
> It's better because it doesn't add any "visible" method to the API, besides
> being beauty IMO =]
Looks beautiful and very Pythonic to me :) +1 for implementing that at
some point in the future.
As for the request for ".ymd(), .hms(), .hmsm(), .ymdhms(),
.ymdhmsm()", this could be solved by slicing the resulting tuple (if
it provides all information), and only providing the "most verbose"
variant (ymdhmsm). Maybe even in the order so that it can be passed to
Python's built-in "datetime.datetime" object constructor?
For the QUrl example in the bug report: One can use urlparse.urlsplit
and urlparse.urlunsplit in the Python standard library for that and
get/pass the full URL from/to QUrl, avoiding the need for API
additions there.
Thanks,
Thomas
Yes -- do note that conversion through tuple() can only be implemented by making QPoint iterable.
> > [2] http://bugs.openbossa.org/show_bug.cgi?id=607
>
> It's something that you can use on your projects but not necessarily need to
> be on PySide itself, IMO your attempt to simplify the code failed, not by your
> bad, but because the original code is already too simple.
Wouldn't it be plain beauty to be able to write a one-line hello world?
with QtGui.QApplication: QtGui.QLabel("Hello world!").show()
:-)
It's not going to halve the number of lines of code of your projects, but it makes PySide easier and plain more fun to use. It's a bit like the lack of qmake project files in Python compared to C++: in the grand scheme of things, it's not much (you could write a 2-line project file), but it makes Python that much more enjoyable to work with as you can write a tiny app from scratch in no time.
> > [3] http://bugs.openbossa.org/show_bug.cgi?id=615
>
> I agree with Matti, there's no need to add yet another incompatibility with
> PyQt for basically no gain at all.
But the API clearly makes no sense. Why should any API defined by PyQt have to be cast in stone and never be improved, especially here where there are no backward-compatibility issues?
Cheers,
Farsmo
2011/1/17 Fars- mo <far...@live.com>:
> Hugo Parente Lima wrote:
>> > [2] http://bugs.openbossa.org/show_bug.cgi?id=607
> >
>> It's something that you can use on your projects but not necessarily need to
>> be on PySide itself, IMO your attempt to simplify the code failed, not by your
>> bad, but because the original code is already too simple.
>
> Wouldn't it be plain beauty to be able to write a one-line hello world?
>
> with QtGui.QApplication: QtGui.QLabel("Hello world!").show()
The one-liner wouldn't work, because the QLabel will be destroyed
after show() was called (because it drops out of context - you have to
keep a reference to it for it to stay alive).
This can be done with a simple context manager, actually:
============
from contextlib import contextmanager
import sys
from PySide.QtGui import *
@contextmanager
def QtApp():
app = QApplication(sys.argv)
yield app
sys.exit(app.exec_())
============
Usage:
with QtApp():
l = QLabel("Hello world!")
l.show()
Or also with access to the QApplication object should the need arise:
with QtApp() as app:
w = QMainWindow()
w.setWindowTitle('blubb')
pb = QPushButton('heya')
pb.clicked.connect(lambda: app.exit(2))
w.setCentralWidget(pb)
w.show()
Have fun :)
Thomas
IMHO this seems to be an incompatibility from PyQt, as there are some
methods that return a tuple (bool, retval) due to the limitation of
not being able to modify an immutable type argument.
--
Lauro Moura
INdT - Instituto Nokia de Tecnologia
Yeah, ymdhmsm can be quite a mouthful, but the tricky thing is that datetime.datetime accepts a ymdhmsu tuple, that is with microseconds and not milliseconds (come to think of it, the name is poor as m could also be interpreted as "microsecond"...)
So if by default we provide a tuple that gives everything including milliseconds, users are likely to fall into that trap and pass it to datetime.datetime unaware of the difference :-(
Perhaps the cleanest solution would be to only provide conversion to datetime.datetime, but datetime.datetime doesn't provide a tuple with sub-second information!
As for ymd and hms, I was thinking about those more for QDate and QTime than for QDateTime.
> For the QUrl example in the bug report: One can use urlparse.urlsplit
> and urlparse.urlunsplit in the Python standard library for that and
> get/pass the full URL from/to QUrl, avoiding the need for API
> additions there.
Very good point. Perhaps it should be checked that QUrl indeed accepts exactly the same inputs as urlparse, and that they will always be split the same way.
Cheers,
Farsmo
I forgot to mention this in the mailing list, but I recently found out that all the enhancements in this thread are dependent on the fact that this behavior gets changed.
I filed this as a bug, although because it's controversial and a pretty big change it will likely become a PSEP:
http://bugs.openbossa.org/show_bug.cgi?id=619
> This can be done with a simple context manager, actually:
>
> ============
> from contextlib import contextmanager
> import sys
>
> from PySide.QtGui import *
>
> @contextmanager
> def QtApp():
> app = QApplication(sys.argv)
> yield app
> sys.exit(app.exec_())
> ============
>
> Usage:
>
> with QtApp():
> l = QLabel("Hello world!")
> l.show()
>
> Or also with access to the QApplication object should the need arise:
>
> with QtApp() as app:
> w = QMainWindow()
> w.setWindowTitle('blubb')
> pb = QPushButton('heya')
> pb.clicked.connect(lambda: app.exit(2))
> w.setCentralWidget(pb)
> w.show()
No, your examples would fall prey to the same issue as the one-liner because if I'm not mistaken, l and w would have ended their lifetimes when the context manager's __exit__ is called.
Cheers,
Farsmo
In this case PyQt has the same prototype as PySide, that is quadToQuad is a 3-argument version that modifies an otherwise unused QTransform (this is because QTransform are mutable, so unlike ints it's possible to pass them by reference).
So there is nothing to prevent PyQt from implementing that API extension.
Cheers,
Farsmo
Sorry, my bad, l and w would still be alive. Good point!