On 2020-06-03 15:29, Victor Stinner wrote:
Hi,
As I wrote in a previous email, the C API of CPython is evolving slowly to hide more and more implementation details. Sadly, some changes break the backward compatibility. Sometimes, a new function must be used, except that the function doesn't exist in older Python versions. > "Update on C API changes to hide implementation details" https://mail.python.org/archives/list/capi-sig@python.org/thread/HYFQHLGNCRF...
Could we do breaking changes within the proper deprecation cycle? And preferably with a PEP?
I see that the current documentation of Py_TYPE, Py_REFCNT ans Py_SIZE suggest that using them as lvalues is perfectly valid. There is no mention that anything changed. I see there was no documentation change even as the breaking code change went in. That's quite unfair to users.
Would it be possible to ship a header file (a single ".h" file) and/or even a dynamic library to provide new functions to old Python versions? I'm not sure if it's possible to ship a header file as a package on PyPI and make it available for C compilers easily without monkey patching distutils. Another option is to encourage Linux distributions to package such header file to make it easy to install on the system.
My project py3c was ment as compatibility headers for the 2→3 transition, but I ended up taking pull requests for newer features as well: https://github.com/encukou/py3c
I haven't figured out how to make the header file easily available from PyPI, however.
Another (worse?) option is to advise authors of each C extension module to vendor a copy of the header file in their project and update it from time to time when needed.
At least in C, we can provide most "missing" functions on older Python versions as macros or static inline functions. Python 3.6 now requires a subset C99 with static inline functions.
The situation is very similar to the Python 2 to Python 3 transition. Benjamin Peterson wrote the "six" which became quickly very popular. First, projects vendored a copy of the "six" module as a submodule of their project (src/project/six.py, imported as "project.six"). Then, slowly, the "six" module was shipped by Linux distributions or installed via "pip install six".
Cython already does that internally. A few examples: https://github.com/cython/cython/blob/57b4bdeb88cc18f8e2f89fe9a7cf506d4ee0f8...
#if PY_VERSION_HEX < 0x030200A4 typedef long Py_hash_t; #define __Pyx_PyInt_FromHash_t PyInt_FromLong #define __Pyx_PyInt_AsHash_t __Pyx_PyIndex_AsHash_t #else #define __Pyx_PyInt_FromHash_t PyInt_FromSsize_t #define __Pyx_PyInt_AsHash_t __Pyx_PyIndex_AsSsize_t #endif
#if CYTHON_COMPILING_IN_LIMITED_API #define __Pyx_PyThreadState_Current PyThreadState_Get() #elif !CYTHON_FAST_THREAD_STATE #define __Pyx_PyThreadState_Current PyThreadState_GET() #elif PY_VERSION_HEX >= 0x03060000 //#elif PY_VERSION_HEX >= 0x03050200 // Actually added in 3.5.2, but compiling against that does not guarantee that we get imported there. #define __Pyx_PyThreadState_Current _PyThreadState_UncheckedGet() #elif PY_VERSION_HEX >= 0x03000000 #define __Pyx_PyThreadState_Current PyThreadState_GET() #else #define __Pyx_PyThreadState_Current _PyThreadState_Current #endif
#if PY_VERSION_HEX >= 0x030900A4 #define __Pyx_SET_REFCNT(obj, refcnt) Py_SET_REFCNT(obj, refcnt) #define __Pyx_SET_SIZE(obj, size) Py_SET_SIZE(obj, size) #else #define __Pyx_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt) #define __Pyx_SET_SIZE(obj, size) Py_SIZE(obj) = (size) #endif
Victor