The Python C API compatibility project is made of two parts:
pythoncapi_compat.h
: Header file providing new functions of the Python C API to old Python versions.upgrade_pythoncapi.py
: Script upgrading C extension modules to newer Python API without losing support for old Python versions. It relies onpythoncapi_compat.h
.
pythoncapi_compat.h
supports Python 3.5 to Python 3.10, Python 2.7,
PyPy 3.6 and PyPy 2.7. A C99 subset is required, like static inline
functions: see PEP 7.
ISO C90 is partially supported for Python 2.7:
avoid mixed declarations and code (GCC
-Werror=declaration-after-statement
flag) and support Visual Studio 2008.
upgrade_pythoncapi.py
requires Python 3.6 or newer.
Homepage: https://github.com/pythoncapi/pythoncapi_compat
Latest header file: https://raw.githubusercontent.com/pythoncapi/pythoncapi_compat/master/pythoncapi_compat.h
This project is distributed under the MIT license.
This project is covered by the PSF Code of Conduct.
Upgrade mod.c
file:
python3 upgrade_pythoncapi.py mod.c
Upgrade all .c
and .h
files of a project:
python3 upgrade_pythoncapi.py directory/
WARNING: files are modified in-place! If a file is modified, the original file
is saved as <filename>.old
.
To see command line options and list available operations, run it with no arguments:
python3 upgrade_pythoncapi.py
For example, to only replace op->ob_type
with Py_TYPE(op)
, use:
python3 upgrade_pythoncapi.py -o Py_TYPE mod.c
Or the opposite, to apply all operations but leave op->ob_type
unchanged,
use:
python3 upgrade_pythoncapi.py -o all,-Py_TYPE mod.c
Most upgrade_pythoncapi.py operations add #include "pythoncapi_compat.h"
.
You may have to copy the pythoncapi_compat.h
header file to your project.
It can be copied from:
https://raw.githubusercontent.com/pythoncapi/pythoncapi_compat/master/pythoncapi_compat.h
upgrade_pythoncapi.py
implements the following operations:
Py_TYPE
:- Replace
op->ob_type
withPy_TYPE(op)
.
- Replace
Py_SIZE
:- Replace
op->ob_size
withPy_SIZE(op)
.
- Replace
Py_REFCNT
:- Replace
op->ob_refcnt
withPy_REFCNT(op)
.
- Replace
Py_SET_TYPE
:- Replace
obj->ob_type = type;
withPy_SET_TYPE(obj, type);
. - Replace
Py_TYPE(obj) = type;
withPy_SET_TYPE(obj, type);
.
- Replace
Py_SET_SIZE
:- Replace
obj->ob_size = size;
withPy_SET_SIZE(obj, size);
. - Replace
Py_SIZE(obj) = size;
withPy_SET_SIZE(obj, size);
.
- Replace
Py_SET_REFCNT
:- Replace
obj->ob_refcnt = refcnt;
withPy_SET_REFCNT(obj, refcnt);
. - Replace
Py_REFCNT(obj) = refcnt;
withPy_SET_REFCNT(obj, refcnt);
.
- Replace
Py_Is
:- Replace
x == Py_None
withPy_IsNone(x)
. - Replace
x == Py_True
withPy_IsTrue(x)
. - Replace
x == Py_False
withPy_IsFalse(x)
. - Replace
x != Py_None
with!Py_IsNone(x)
. - Replace
x != Py_True
with!Py_IsTrue(x)
. - Replace
x != Py_False
with!Py_IsFalse(x)
.
- Replace
PyObject_NEW
:- Replace
PyObject_NEW(...)
withPyObject_New(...)
. - Replace
PyObject_NEW_VAR(...)
withPyObject_NewVar(...)
.
- Replace
PyMem_MALLOC
:- Replace
PyMem_MALLOC(n)
withPyMem_Malloc(n)
. - Replace
PyMem_REALLOC(ptr, n)
withPyMem_Realloc(ptr, n)
. - Replace
PyMem_FREE(ptr)
,PyMem_DEL(ptr)
andPyMem_Del(ptr)
. withPyMem_Free(n)
.
- Replace
PyObject_MALLOC
:- Replace
PyObject_MALLOC(n)
withPyObject_Malloc(n)
. - Replace
PyObject_REALLOC(ptr, n)
withPyObject_Realloc(ptr, n)
. - Replace
PyObject_FREE(ptr)
,PyObject_DEL(ptr)
andPyObject_Del(ptr)
. withPyObject_Free(n)
.
- Replace
PyFrame_GetBack
:- Replace
frame->f_back
with_PyFrame_GetBackBorrow(frame)
.
- Replace
PyFrame_GetCode
:- Replace
frame->f_code
with_PyFrame_GetCodeBorrow(frame)
.
- Replace
PyThreadState_GetInterpreter
:- Replace
tstate->interp
withPyThreadState_GetInterpreter(tstate)
.
- Replace
PyThreadState_GetFrame
:- Replace
tstate->frame
with_PyThreadState_GetFrameBorrow(tstate)
.
- Replace
Some functions related to frame objects are not available on PyPy.
PyObject* Py_NewRef(PyObject *obj); PyObject* Py_XNewRef(PyObject *obj); int Py_Is(PyObject *x, PyObject *y); int Py_IsNone(PyObject *x); int Py_IsTrue(PyObject *x); int Py_IsFalse(PyObject *x); int PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value);
void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt); void Py_SET_TYPE(PyObject *ob, PyTypeObject *type); void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size); int Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type); PyObject* PyObject_CallNoArgs(PyObject *func); PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg);
PyCodeObject* PyFrame_GetCode(PyFrameObject *frame); // Not available on PyPy PyFrameObject* PyFrame_GetBack(PyFrameObject *frame);
// Not available on PyPy PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate); PyInterpreterState* PyThreadState_GetInterpreter(PyThreadState *tstate); // Availability: Python 3.7. Not available on PyPy. uint64_t PyThreadState_GetID(PyThreadState *tstate);
PyInterpreterState* PyInterpreterState_Get(void);
// Not available on PyPy. int PyObject_GC_IsTracked(PyObject* obj); // Availability: Python 3.4. Not available on PyPy. int PyObject_GC_IsFinalized(PyObject *obj);
int PyModule_AddType(PyObject *module, PyTypeObject *type);
Py_SETREF(op, op2) Py_XSETREF(op, op2)
Py_UNUSED(name)
To ease migration of C extensions to the new C API, a variant is provided to return borrowed references rather than strong references:
// Similar to "Py_INCREF(ob); return ob;" PyObject* _Py_StealRef(PyObject *ob); // Similar to "Py_XINCREF(ob); return ob;" PyObject* _Py_XStealRef(PyObject *ob); // PyThreadState_GetFrame(). Not available on PyPy. PyFrameObject* _PyThreadState_GetFrameBorrow(PyThreadState *tstate) // PyFrame_GetCode() PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame) // PyFrame_GetBack(). Not available on PyPy. PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame)
For example, tstate->frame
can be replaced with
_PyThreadState_GetFrameBorrow(tstate)
to avoid accessing directly
PyThreadState.frame
member.
These functions are only available in pythoncapi_compat.h
and are not
part of the Python C API.
Run tests:
python3 runtests.py
Only test the current Python version, don't test multiple Python versions
(-c
, --current
):
python3 runtests.py --current
Verbose mode (-v
, --verbose
):
python3 runtests.py --verbose
See tests in the tests/
subdirectory.
- PEP 620 -- Hide implementation details from the C API
- Make structures opaque
- Python/C API Reference Manual
- HPy: a better API for Python
- Cython: C-extensions for Python
- ModuleSetupCode.c
provides functions like
__Pyx_SET_REFCNT()
- Cython doesn't use pythoncapi_compat.h: see Cython issue #3934
- ModuleSetupCode.c
provides functions like
- Old 2to3c project by David Malcolm which uses Coccinelle to ease migration of C extensions from Python 2 to Python 3. See also 2to3c: an implementation of Python's 2to3 for C code article (2009).
- numpy has its own compatibility layer,
npy_pycompat.h
andnpy_3kcompat.h
header files. It supports more C compilers than pythoncapi_compat.h: it supports__STRICT_ANSI__
(ISO C90) for example. Reject PR 18713: MAINT: Use pythoncapi_compat.h in npy_3kcompat.h (when it was rejected, numpy still had code for compatibility with Python 2.7).
- 2021-04-09: Add Py_Is(), Py_IsNone(), Py_IsTrue(), Py_IsFalse() functions.
- 2021-04-01:
- Add
Py_SETREF()
,Py_XSETREF()
andPy_UNUSED()
. - Add PyPy support.
- Add
- 2021-01-27: Fix compatibility with Visual Studio 2008 for Python 2.7.
- 2020-11-30: Creation of the
upgrade_pythoncapi.py
script. - 2020-06-04: Creation of the
pythoncapi_compat.h
header file.
- bitarray:
bitarray/_bitarray.c
usesPy_SET_SIZE()
(pythoncapi_compat.h copy) - immutables:
immutables/_map.c
usesPy_SET_SIZE()
(pythoncapi_compat.h copy) - Mercurial (hg) uses
Py_SET_TYPE()
(commit, pythoncapi_compat.h copy) - python-zstandard
uses
Py_SET_TYPE()
andPy_SET_SIZE()
(commit): Mercurial extension.