diff options
Diffstat (limited to 'sources/pyside6/doc/developer/feature-motivation.rst')
-rw-r--r-- | sources/pyside6/doc/developer/feature-motivation.rst | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/sources/pyside6/doc/developer/feature-motivation.rst b/sources/pyside6/doc/developer/feature-motivation.rst new file mode 100644 index 000000000..1509ea724 --- /dev/null +++ b/sources/pyside6/doc/developer/feature-motivation.rst @@ -0,0 +1,303 @@ +.. _feature-why: + +Why do we have a __feature__? +============================= + + +History +------- + +In PySide user story PYSIDE-1019, we tested certain ways to +make PySide more pythonic. The first idea was to support some +way to allow for ``snake_case`` function names. + +This feature is possible with relatively low compatibility +problems, because having the same function with different names +would be not so nice, but a possible low-effort solution. + +When going to ``true_property``, things become different. When we +support properties as first class objects instead of getter +and setter functions, we get a conflict, because a function +cannot act as a property (no braces) and be a function at the +same time. + +This consideration led us to the idea: +Features must be selectable per-module. + + +Why are features selectable per-module? +--------------------------------------- + +Suppose you have some pre-existing code. Maybe you use some downloaded +code or you generated an interface file. When you now decide to +use a feature, you don't want all this existing stuff to become +incorrect. By using the statement + +.. code-block:: python + + from __feature__ import ... + +you declare that this module uses some feature. Other modules will not +be influenced by this decision and can stay unchanged. + + +Why dunder, and why not __future__? +----------------------------------- + +Especially in Python 2, but in a few cases also in Python 3, there is +the future statement + +.. code-block:: python + + from __future__ import ... + +That is a statement that can only appear at the beginning of a module, +and it switches how the Python parser works. + +Our first idea was to mimick this behavior for PySide, although we are +a bit cheating: The feature statement is not a syntactical construct, +and we cannot easily forbid that it is in the middle of a module. + +We then realized that the intention of Python's ``__future__`` import and +PySide's ``__feature__`` import are different: While Python implies by +``__future__`` some improvement, we do not want to associate with +``__feature__``. We simply think that some users who come from Python may +like our features, while others are used to the C++ convention and +consider something that deviates from the Qt documentation as drawback. + +The intention to use the ``from __feature__ import ...`` notation was the hope that +people see the similarity to Python's ``__future__`` statement and put that import +at the beginning of a module to make it very visible that this module +has some special global differences. + + +The snake_case feature +====================== + +By using the statement + +.. code-block:: python + + from __feature__ import snake_case + +all methods of all classes used in this module are changing their name. + +The algorithm to change names is this: + +* if the name has less than 3 chars, or +* if two upper chars are adjacent, or +* if the name starts with ``gl`` (which marks OpenGL), +* the name is returned unchanged. Otherwise +* a single upper char ``C`` is replaced by ``_c`` + + +The true_property feature +========================= + +By using the statement + +.. code-block:: python + + from __feature__ import true_property + +all methods of all classes used in this module which are declared in the Qt +documentation as property become real properties in Python. + +This feature is incompatible with the past and cannot coexist; it is +the reason why the feature idea was developed at all. + + +Normal Properties +----------------- + +Normal properties have the same name as before: + +.. code-block:: python + + QtWidgets.QLabel().color() + +becomes as property + +.. code-block:: python + + QtWidgets.QLabel().color + +When there is also a setter method, + +.. code-block:: python + + QtWidgets.QLabel().setColor(value) + +becomes as property + +.. code-block:: python + + QtWidgets.QLabel().color = value + +Normal properties swallow the getter and setter functions and replace +them by the property object. + + +Special Properties +------------------ + +Special properties are those with non-standard names. + +.. code-block:: python + + QtWidgets.QLabel().size() + +becomes as property + +.. code-block:: python + + QtWidgets.QLabel().size + +But here we have no setSize function, but + +.. code-block:: python + + QtWidgets.QLabel().resize(value) + +which becomes as property + +.. code-block:: python + + QtWidgets.QLabel().size = value + +In that case, the setter does not become swallowed, because so many +people are used to the ``resize`` function. + + +Class properties +---------------- + +It should be mentioned that we not only support regular properties +as they are known from Python. There is also the concept of class +properties which always call their getter and setter: + +A regular property like the aforementioned ``QtWidgets.QLabel`` has +this visibility: + +.. code-block:: python + + >>> QtWidgets.QLabel.size + <property object at 0x113a23540> + >>> QtWidgets.QLabel().size + PySide6.QtCore.QSize(640, 480) + +A class property instead is also evaluated without requiring an instance: + +.. code-block:: python + + >>> QtWidgets.QApplication.windowIcon + <PySide6.QtGui.QIcon(null) at 0x113a211c0> + +You can only inspect it if you go directly to the right class dict: + +.. code-block:: python + + >>> QtGui.QGuiApplication.__dict__["windowIcon"] + <PySide6.PyClassProperty object at 0x114fc5270> + + +About Property Completeness +--------------------------- + +There are many properties where the Python programmer agrees that these +functions should be properties, but a few are not properties, like + +.. code-block:: python + + >>> QtWidgets.QMainWindow.centralWidget + <method 'centralWidget' of 'PySide6.QtWidgets.QMainWindow' objects> + +We are currently discussing if we should correct these rare cases, as they +are probably only omissions. Having to memorize the missing properties +seems to be quite cumbersome, and instead of looking all properties up in +the Qt documentation, it would be easier to add all properties that +should be properties and are obviously missing. + + +Name Clashes and Solution +------------------------- + +There are some rare cases where a property already exists as a function, +either with multiple signatures or having parameters. +This is not very nice in C++ as well, but for Python this is forbidden. +Example: + +.. code-block:: python + + >>> from PySide6 import * + >>> from PySide6.support.signature import get_signature + >>> import pprint + >>> pprint.pprint(get_signature(QtCore.QTimer.singleShot)) + [<Signature (arg__1: int, arg__2: Callable) -> None>, + <Signature (msec: int, receiver: PySide6.QtCore.QObject, member: bytes) -> None>, + <Signature (msec: int, timerType: PySide6.QtCore.Qt.TimerType, + receiver: PySide6.QtCore.QObject, member: bytes) -> None>] + +When creating this property, we respect the existing function and use a slightly +different name for the property by appending an underscore. + +.. code-block:: python + + >>> from __feature__ import true_property + >>> QtCore.QTimer.singleShot_ + <property object at 0x118e5f8b0> + +We hope that these clashes can be removed in future Qt versions. + + +The __feature__ import +====================== + +The implementation of ``from __feature__ import ...`` is built by a slight +modification of the ``__import__`` builtin. We made that explicit by assigning +variables in the builtin module. This modification takes place at |project| +import time: + +* The original function in ``__import__`` is kept in ``__orig_import__``. +* The new function is in ``__feature_import__`` and assigned to ``__import__``. + +This function calls the Python function ``PySide6.support.__feature__.feature_import`` +first, and falls back to ``__orig_import__`` if feature import is not applicable. + + +Overriding __import__ +--------------------- + +This is not recommended. Import modifications should be done using import hooks, +see the Python documentation on `Import-Hooks`_. + +If you would like to modify ``__import__`` anyway without destroying the features, +please override just the ``__orig_import__`` function. + + +IDEs and Modifying Python stub files +------------------------------------ + +|project| comes with pre-generated ``.pyi`` stub files in the same location as +the binary module. For instance, in the site-packages directory, you can find +a ``QtCore.pyi`` file next to ``QtCore.abi3.so`` or ``QtCore.pyd`` on Windows. + +When using ``__feature__`` often with common IDEs, you may want to provide +a feature-aware version of ``.pyi`` files to get a correct display. The simplest +way to change them all in-place is the command: + +.. code-block:: bash + + pyside6-genpyi all --feature snake_case true_property + + +Using __feature__ with UIC files +-------------------------------- + +Features can be freely used together with generated UIC files. The UIC files +are _not_ converted, intentionally. Mixing them with feature selections in other +Python modules should always work, because switching will happen as needed, selected +by the currently active module. (Please report to us if this fails for an example) + + +.. _`Import-Hooks`: https://docs.python.org/3/reference/import.html#import-hooks |