Hi,

My reply to Matti's comments got commented (with '>'s), sorry about!

BTW Keep in mind that the number of PySide programs is probably quite
small at the moment, so _most_ PySide programs have yet to be written
(or ported from PyQt). In view of this I think it could easily be the
case that Python 3 is overwhelmingly the most common Python version used
by most PySide programs. I think this is even more of an argument to
adopt API 2 and to make PySide for Python 3 as Pythonic as possible.

Here's a revised PSEP, embodying the feedback I've read so far.

I've added to the "Rationale" section, added a new "Possible QVariant
Exception" section, and added to the "Changing APIs" section. I also
dropped the section on "Exceptions".

----------------------------------------------------------------------
PSEP: 
Title: More Pythonic API for Python 3/PySide
Version: $Revision$
Last-Modified: $Date$
Author: Mark Summerfield <[email protected]>
Status: 
Type: 
Content-Type: text/x-rst
Created: 14-Apr-2010
Post-History: 14-Apr-2010, 15-Apr-2010, 16-Apr-2010

Abstract
========

PyQt4 provides two different (and incompatible) APIs. API 1 is the original
API and the one supported by PySide. API 2 is a new Python 3-specific API
that is much more Pythonic. This PSEP proposes that PySide adopt PyQt4's
API 2 for when PySide is used with Python 3.


Rationale
=========

API 1 is useful for those using PySide to prototype C++/Qt programs since
it is very close to the C++/Qt API. However, for those who want to create
PySide programs in their own right, and especially for existing Python
programmers, API 1 is cumbersome and un-Pythonic when it comes to handling
QStrings and QVariants, both of which are used a lot.

If PySide were to support API 2, it will make PySide much more attractive
to Python 3 programmers.

Also, supporting API 2 will mean that existing Python 3/PyQt4 programs that
use API 2 by default will be able to switch to PySide.

One key purpose of API 2 is to avoid the need for Python programmers to
have to worry or even know about QString or QVariant. For Python
programmers str is the string they are used to so translation to/from
QString should be transparent and automatic. Similarly, why should
Python programmers have to know about QVariant when that is a C++
workaround for C++'s lack of support for duck typing etc.? Again,
translation to/from QVariant should be transparent and automatic. API 2
achieves this and eliminates QString and QVariant.


Python 3-Specific Differences
=============================


No QString, or QVariant
-----------------------

The key difference between the APIs is that API 2 does not make QString
or QVariant available to programmers, instead using Python 3's native
str type for strings, and any Python class for QVariant.

The benefit of API 2 in this regard is that programmers never have to
explicitly convert between QString and str, so programmers never get
caught out accidentally trying to use a QString method on a str or vice
versa. For the Qt APIs that accept or return QVariants, API 2 allows
programmers to pass any Python object and when a QVariant is returned
from Qt it is automatically converted to its original Python type (with
an invalid QVariant mapped to None).

For QString, API 2 goes beyond simply replacing QString with str. In
particular, API 2 also automatically converts QChar and QStringRef to and
from strs. It also converts QStringList to and from a Python list of strs.
(PyQt4's API 2 does not implement QLatin1Char, QLatin1String, or
QStringMatcher.)

Native C++/Qt QStrings are mutable, so PyQt4's API 2 has changed the
signatures of some of the affected functions to return a str (or a tuple
including a str) when in the C++/Qt API a QString parameter would have been
modified.

In addition, API 2 has added two methods to QFontMetrics and QFontMetricsF,
widthChar() and boundingRectChar(), which take a str of length one and call
QFontMetrics::width() and QFontMetrics::boundingRect() with a QChar
argument. These were added because the width of a single character may be
different from that of a string that contains a single character, even if
the characters are the same.


Possible QVariant Extension
---------------------------

Richard Dale points out that there may be cases (e.g., QtDBus), where
the type wrapped inside a QVariant matters. In API 2, wherever a
QVariant is passed to a function we can pass any Python object instead.
Richard has suggested two possible syntaxes for indicating the type:

    function(QVariant("ushort", value)) # API 1-ish
    function(("ushort", value))         # API 2-ish

For API 2 the second syntax is the only viable one (of these two) since
QVariant doesn't explicitly exist in API 2. However if this syntax were
adopted, it means that tuples cannot be passed to functions accepting a
QVariant. Lauro Moura suggested supporting both the above syntaxes.

It is possible to keep QVariant out of API 2 and still be able to solve
the problem that Richard raised. Here is an alternative syntax:

    function(qVariant("ushort", value))

In other words, a dedicated function, qVariant(), that _always_ takes
two arguments (type, value). This would avoid the need to bring QVariant
back into API 2 and would also mean that tuples (like lists and dicts)
could be passed where a QVariant was expected without formality. With
this syntax, in most cases Python programmers would just pass Python
objects where QVariants were required, but in those rare cases where the
C++ type matters, the qVariant() function could be used.


Changes to Other Types
----------------------

API 2 also affects some other types, as follows.

Where QByteArrays are expected, bytes objects can be used, but unlike
QString, QByteArray has been retained to ease porting from API 1, so in
API 2 byte sequences can be represented by QByteArray, bytes, and
bytearray objects.

QDate, QDateTime, QTime, and QUrl's __hash__() methods return a string
representation so that identical dates (and identical date/times or times
or URLs) will have identical hash values.

QTextStream's bin(), hex(), and oct() functions have been renamed bin_(),
hex_(), and oct_(), to avoid conflicting with Python's built-ins of the
same names.


Support for Keyword Arguments
=============================

PyQt 4.7 adds support for keyword arguments which is very convenient and
much more Pythonic. 

This change is not Python 3-specific, but it does represent an extension to
the PyQt APIs that a compatible PySide ought to implement.

One problem with this is that while changes to argument names don't affect
C++/Qt, they would break the Python API. Furthermore, since changing
argument names is harmless in C++/Qt, such changes do take place between Qt
versions. This means that for PySide, sensible names much be used in the
first place---and stuck to. Naturally, for PyQt compatibility, PySide ought
to use the same names as PyQt.

PyQt does not integrate Python and Qt's property systems, but it does allow
Qt properties to be set when an object is constructed using keyword
arguments (i.e., where the keyword is the name of a Qt property). Also,
PyQt provides the pyqtConfigure() method for all objects that have
Qt properties: this method can be called at any time and the object's Qt
properties set by passing it keyword arguments.

(This feature could be put in a separate PSEP since it is not Python
3-specific.)


Changing APIs
=============

API 1 is the default for PyQt 4.x with Python 2.x, and for PyQt 4.0-4.5
with Python 3.x, and is the API used by PySide.

API 2 is the default for PyQt 4.6+ with Python 3.x.

PyQt provides a means of changing the API on a per-class basis.

PySide could offer a similar mechanism, but this would mean that
programmers might end up with Python 3/PySide programs that used the two
different APIs, one in some modules, and the other in other modules.

PySide should not offer such a mechanism, but should instead support API
1 for Python 2.x and API 2 for Python 3.x to avoid confusion. (If users
demanded an API changing mechanism, it could be added later.)

This means that there is a simple logical rule:

    Python 2 == API 1
    Python 3 == API 2

Matti Airas pointed out a disadvantage of this simplicity: that it means
that large applications need to be ported in one big go rather than
module by module (which using a PyQt-like API changing API would allow).
However, Detlev Offenbach questioned how difficult it would be in
practice, even with a large application. He cited the example of the
Eric IDE (which is v. large), pointing out that the conversion from
Python 2 with PyQt API 1 to Python 3 with API 2 took only three weeks,
with most of the problems unrelated to Python 3 or API 2.


Compatibility
=============

The proposed API 2 is incompatible with API 1, but since PySide is not
available for Python 3 yet, the incompatibility will only affect those
porting from Python 2 to Python 3. Such porting affects Python programs
generally, and also PyQt programs, so does not add to (or reduce) the
porting burden.


References
==========

`PyQt v4 Reference Manual: Potential Incompatibilities section, Selecting
Incompatible APIs section, Support for Keyword Arguments section, and
Support for Qt Properties section
<http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/pyqt4ref.html/>`_.


Contributors
============

Richard Dale and Detlev Offenbach pointed out that QByteArray had been
retained in API 2.

Richard Dale and Lauro Moura gave suggestions regarding QVariant.

The section of Exceptions was dropped as a result of feedback from Matti
Airas; Matti also added to the discussion on Changing APIs.

Detlev Offenbach also contributed to the discussion on Changing APIs.


Copyright
=========

This document has been placed in the public domain.

----------------------------------------------------------------------


-- 
Mark Summerfield, Qtrac Ltd, www.qtrac.eu
    C++, Python, Qt, PyQt - training and consultancy
        "Programming in Python 3 (Second Edition)" - ISBN 0321680561
_______________________________________________
PySide mailing list
[email protected]
http://lists.openbossa.org/listinfo/pyside

Reply via email to