aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/developer/feature-motivation.rst
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/doc/developer/feature-motivation.rst')
-rw-r--r--sources/pyside6/doc/developer/feature-motivation.rst303
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