How about *not* asking for an exception and just following the PEP 387
process? Is that really too burdensome?

On Tue, Nov 30, 2021 at 10:30 AM Victor Stinner <vstin...@python.org> wrote:

> Hi,
>
> I propose to disallow using macros as l-value.  Read and comment the
> plain text below, or read the PEP 674 online,
> https://python.github.io/peps/pep-0674/
>
> While I'm not sure that the proposed changes are really controversial,
> I decided to write a formal PEP since the incompatible changes are not
> following the PEP 387 deprecation process and so a format PEP 387
> exception is better. Well, the list of modified macros is also quite
> long. Moreover, a PEP is a way to document and announce the changes
> ;-)
>
> The Py_TYPE() and Py_SIZE() changes are already approved the Steering
> Council, but I prefer to list them in the PEP:
> https://github.com/python/steering-council/issues/79
>
> In practice, I'm only aware of 4 projects impacted by these changes,
> and I wrote the pythoncapi_compat project which updates automatically
> C extensions: add Python 3.11 support, without losing support for
> older Python versions. I already prepared major projects like Cython
> and numpy for these changes (in total, 14 impacted projects have
> already been updated).
>
> Victor
>
> ---
>
> PEP: 674
> Title: Disallow using macros as l-value
> Author: Victor Stinner <vstin...@python.org>
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Created: 30-Oct-2021
> Python-Version: 3.11
>
> Abstract
> ========
>
> Incompatible C API change disallowing using macros as l-value to allow
> evolving CPython internals and to ease the C API implementation on other
> Python implementation.
>
> In practice, the majority of projects impacted by these incompatible
> changes should only have to make two changes:
>
> * Replace ``Py_TYPE(obj) = new_type;``
>   with ``Py_SET_TYPE(obj, new_type);``.
> * Replace ``Py_SIZE(obj) = new_size;``
>   with ``Py_SET_SIZE(obj, new_size);``.
>
>
> Rationale
> =========
>
> Using a macro as a l-value
> --------------------------
>
> In the Python C API, some functions are implemented as macro because
> writing a macro is simpler than writing a regular function. If a macro
> exposes directly a struture member, it is technically possible to use
> this macro to not only get the structure member but also set it.
>
> Example with the Python 3.10 ``Py_TYPE()`` macro::
>
>     #define Py_TYPE(ob) (((PyObject *)(ob))->ob_type)
>
> This macro can be used as a **r-value** to **get** an object type::
>
>     type = Py_TYPE(object);
>
> It can also be used as **l-value** to **set** an object type::
>
>     Py_TYPE(object) = new_type;
>
> It is also possible to set an object reference count and an object size
> using ``Py_REFCNT()`` and ``Py_SIZE()`` macros.
>
> Setting directly an object attribute relies on the current exact CPython
> implementation. Implementing this feature in other Python
> implementations can make their C API implementation less efficient.
>
> CPython nogil fork
> ------------------
>
> Sam Gross forked Python 3.8 to remove the GIL: the `nogil branch
> <https://github.com/colesbury/nogil/>`_. This fork has no
> ``PyObject.ob_refcnt`` member, but a more elaborated implementation for
> reference counting, and so the ``Py_REFCNT(obj) = new_refcnt;`` code
> fails with a compiler error.
>
> Merging the nogil fork into the upstream CPython main branch requires
> first to fix this C API compatibility issue. It is a concrete example of
> a Python optimization blocked indirectly by the C API.
>
> This issue was already fixed in Python 3.10: the ``Py_REFCNT()`` macro
> has been already modified to disallow using it as a l-value.
>
> HPy project
> -----------
>
> The `HPy project <https://hpyproject.org/>`_ is a brand new C API for
> Python using only handles and function calls: handles are opaque,
> structure members cannot be accessed directly,and pointers cannot be
> dereferenced.
>
> Disallowing the usage of macros as l-value helps the migration of
> existing C extensions to HPy by reducing differences between the C API
> and the HPy API.
>
> PyPy cpyext module
> ------------------
>
> In PyPy, when a Python object is accessed by the Python C API, the PyPy
> ``cpyext`` module has to convert PyPy object to a CPython object. While
> PyPy objects are designed to be efficient with the PyPy JIT compiler,
> CPython objects are less efficient and increase the memory usage.
>
> This PEP alone is not enough to get rid of the CPython objects in the
> PyPy ``cpyext`` module, but it is a step towards this long term goal.
> PyPy already supports HPy which is a better solution in the long term.
>
>
> Specification
> =============
>
> Disallow using macros as l-value
> --------------------------------
>
> PyObject and PyVarObject macros
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> * ``Py_TYPE()``: ``Py_SET_TYPE()`` must be used instead
> * ``Py_SIZE()``: ``Py_SET_SIZE()`` must be used instead
>
> "GET" macros
> ^^^^^^^^^^^^
>
> * ``PyByteArray_GET_SIZE()``
> * ``PyBytes_GET_SIZE()``
> * ``PyCFunction_GET_CLASS()``
> * ``PyCFunction_GET_FLAGS()``
> * ``PyCFunction_GET_FUNCTION()``
> * ``PyCFunction_GET_SELF()``
> * ``PyCell_GET()``
> * ``PyCode_GetNumFree()``
> * ``PyDict_GET_SIZE()``
> * ``PyFunction_GET_ANNOTATIONS()``
> * ``PyFunction_GET_CLOSURE()``
> * ``PyFunction_GET_CODE()``
> * ``PyFunction_GET_DEFAULTS()``
> * ``PyFunction_GET_GLOBALS()``
> * ``PyFunction_GET_KW_DEFAULTS()``
> * ``PyFunction_GET_MODULE()``
> * ``PyHeapType_GET_MEMBERS()``
> * ``PyInstanceMethod_GET_FUNCTION()``
> * ``PyList_GET_SIZE()``
> * ``PyMemoryView_GET_BASE()``
> * ``PyMemoryView_GET_BUFFER()``
> * ``PyMethod_GET_FUNCTION()``
> * ``PyMethod_GET_SELF()``
> * ``PySet_GET_SIZE()``
> * ``PyTuple_GET_SIZE()``
> * ``PyUnicode_GET_DATA_SIZE()``
> * ``PyUnicode_GET_LENGTH()``
> * ``PyUnicode_GET_LENGTH()``
> * ``PyUnicode_GET_SIZE()``
> * ``PyWeakref_GET_OBJECT()``
>
> "AS" macros
> ^^^^^^^^^^^
>
> * ``PyByteArray_AS_STRING()``
> * ``PyBytes_AS_STRING()``
> * ``PyFloat_AS_DOUBLE()``
> * ``PyUnicode_AS_DATA()``
> * ``PyUnicode_AS_UNICODE()``
>
> PyUnicode macros
> ^^^^^^^^^^^^^^^^
>
> * ``PyUnicode_1BYTE_DATA()``
> * ``PyUnicode_2BYTE_DATA()``
> * ``PyUnicode_4BYTE_DATA()``
> * ``PyUnicode_DATA()``
> * ``PyUnicode_IS_ASCII()``
> * ``PyUnicode_IS_COMPACT()``
> * ``PyUnicode_IS_READY()``
> * ``PyUnicode_KIND()``
> * ``PyUnicode_READ()``
> * ``PyUnicode_READ_CHAR()``
>
> PyDateTime "GET" macros
> ^^^^^^^^^^^^^^^^^^^^^^^
>
> * ``PyDateTime_DATE_GET_FOLD()``
> * ``PyDateTime_DATE_GET_HOUR()``
> * ``PyDateTime_DATE_GET_MICROSECOND()``
> * ``PyDateTime_DATE_GET_MINUTE()``
> * ``PyDateTime_DATE_GET_SECOND()``
> * ``PyDateTime_DATE_GET_TZINFO()``
> * ``PyDateTime_DELTA_GET_DAYS()``
> * ``PyDateTime_DELTA_GET_MICROSECONDS()``
> * ``PyDateTime_DELTA_GET_SECONDS()``
> * ``PyDateTime_GET_DAY()``
> * ``PyDateTime_GET_MONTH()``
> * ``PyDateTime_GET_YEAR()``
> * ``PyDateTime_TIME_GET_FOLD()``
> * ``PyDateTime_TIME_GET_HOUR()``
> * ``PyDateTime_TIME_GET_MICROSECOND()``
> * ``PyDateTime_TIME_GET_MINUTE()``
> * ``PyDateTime_TIME_GET_SECOND()``
> * ``PyDateTime_TIME_GET_TZINFO()``
>
> PyDescr macros
> ^^^^^^^^^^^^^^
>
> * ``PyDescr_NAME()``
> * ``PyDescr_TYPE()``
>
> Port C extensions to Python 3.11
> --------------------------------
>
> In practice, the majority of projects impacted by these PEP incompatible
> changes should only have to make two changes:
>
> * Replace ``Py_TYPE(obj) = new_type;``
>   with ``Py_SET_TYPE(obj, new_type);``.
> * Replace ``Py_SIZE(obj) = new_size;``
>   with ``Py_SET_SIZE(obj, new_size);``.
>
> The `pythoncapi_compat project
> <https://github.com/pythoncapi/pythoncapi_compat>`_ can be used to
> update automatically C extensions: add Python 3.11 support without
> losing support with older Python versions. The project provides a header
> file which provides ``Py_SET_REFCNT()``, ``Py_SET_TYPE()`` and
> ``Py_SET_SIZE()`` functions to Python 3.8 and older.
>
> PyTuple_GET_ITEM() and PyList_GET_ITEM()
> ----------------------------------------
>
> The ``PyTuple_GET_ITEM()`` and ``PyList_GET_ITEM()`` macros are left
> unchanged.
>
> The code pattern ``&PyTuple_GET_ITEM(tuple, 0)`` and
> ``&PyList_GET_ITEM(list, 0)`` is still commonly used to get access to
> the inner ``PyObject**`` array.
>
> Changing these macros would require to add a new API to get access to
> the inner array which is out of the scope of this PEP.
>
>
> Backwards Compatibility
> =======================
>
> The proposed C API changes are backward incompatible on purpose.  In
> practice, only a minority of third party projects are affected (16
> projects are known to be broken) and `most of them have already been
> updated for these changes
> <https://bugs.python.org/issue39573#msg401378>`__ (12 on 16).
>
> Most projects are broken by ``Py_TYPE()`` and ``Py_SIZE()`` changes.
> These two macros have been converted to static inline macro in Python
> 3.10 alpha versions, but the change had to be reverted since it broke
> too many projects. In the meanwhile, many projects, like Cython, have
> been prepared for this change by using ``Py_SET_TYPE()`` and
> ``Py_SET_SIZE()``. For example, projects using Cython only have to
> regenerate their outdated generated C code to become compatible.
>
> For the "GET" functions like ``PyDict_GET_SIZE()``, no project in the PyPI
> top 5000 projects use these functions as l-value.
>
> The ``PyFloat_AS_DOUBLE()`` function is not used as a l-value in the
> PyPI top 5000 projects.
>
> The ``PyBytes_AS_STRING()`` and ``PyByteArray_AS_STRING()`` are used as
> l-value but only to modify string characters, not to override the
> ``PyBytesObject.ob_sval`` or ``PyByteArrayObject.ob_start`` member.
> For example, Cython uses the following code which remains valid::
>
>     PyByteArray_AS_STRING(string)[i] = (char) v;
>
> This change does not follow the PEP 387 deprecation process. There is no
> known way to emit a deprecation warning when a macro is used as a
> l-value, but not when it's used differently (ex: r-value).
>
>
> Rejected Idea: Leave the macros as they are
> ===========================================
>
> The documentation of each function can discourage developers to use
> macros to modify Python objects.
>
> If these is a need to make an assignment, a setter function can be added
> and the macro documentation can require to use the setter function. For
> example, a ``Py_SET_TYPE()`` function has been added to Python 3.9 and
> the ``Py_TYPE()`` documentation now requires to use the
> ``Py_SET_TYPE()`` function to set an object type.
>
> If developers use macros as l-value, it's their responsibility when
> their code breaks, not the Python responsibility. We are operating under
> the consenting adults principle: we expect users of the Python C API to
> use it as documented and expect them to take care of the fallout, if
> things break when they don't.
>
> This idea was rejected because only few developers read the
> documentation, and only a minority is tracking changes of the Python C
> API documentation. The majority of developers are only using CPython and
> so are not aware of compatibility issues with other Python
> implementations.
>
> Moreover, continuing to allow using macros as l-value does not solve
> issues of the nogil, PyPy and HPy projects.
>
>
> Macros already modified
> =======================
>
> The following C API macros have already been modified to disallow using
> them as l-value:
>
> * ``PyCell_SET()``
> * ``PyList_SET_ITEM()``
> * ``PyTuple_SET_ITEM()``
> * ``Py_REFCNT()`` (Python 3.10): ``Py_SET_REFCNT()`` must be used
> * ``_PyGCHead_SET_FINALIZED()``
> * ``_PyGCHead_SET_NEXT()``
> * ``asdl_seq_GET()``
> * ``asdl_seq_GET_UNTYPED()``
> * ``asdl_seq_LEN()``
> * ``asdl_seq_SET()``
> * ``asdl_seq_SET_UNTYPED()``
>
> For example, ``PyList_SET_ITEM(list, 0, item) < 0`` now fails with a
> compiler error as expected.
>
>
> References
> ==========
>
> * `Python C API: Add functions to access PyObject
>   <https://vstinner.github.io/c-api-abstract-pyobject.html>`_ (October
>   2021) article by Victor Stinner
> * `[C API] Disallow using PyFloat_AS_DOUBLE() as l-value
>   <https://bugs.python.org/issue45476>`_
>   (October 2021)
> * `[capi-sig] Py_TYPE() and Py_SIZE() become static inline functions
>   <
> https://mail.python.org/archives/list/capi-...@python.org/thread/WGRLTHTHC32DQTACPPX36TPR2GLJAFRB/
> >`_
>   (September 2021)
> * `[C API] Avoid accessing PyObject and PyVarObject members directly:
> add Py_SET_TYPE() and Py_IS_TYPE(), disallow Py_TYPE(obj)=type
>   <https://bugs.python.org/issue39573>`__ (February 2020)
> * `bpo-30459: PyList_SET_ITEM  could be safer
>   <https://bugs.python.org/issue30459>`_ (May 2017)
>
>
> Copyright
> =========
>
> This document is placed in the public domain or under the
> CC0-1.0-Universal license, whichever is more permissive.
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-le...@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/KPIJPPJ6XVNOLGZQD2PFGMT7LBJMTTCO/
> Code of Conduct: http://python.org/psf/codeofconduct/
>


-- 
--Guido van Rossum (python.org/~guido)
*Pronouns: he/him **(why is my pronoun here?)*
<http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/AZJCLVV7TLBRGKL7URUBO5PCZ4GEZRSV/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to