Here is a report on my 18 commits merged into Python in February 2025:

  • Reorganize C API tests
  • Use PyErr_FormatUnraisable()
  • Reorganize includes
  • C API: Remove PySequence_Fast()
  • C API: Fix function signatures
  • C API: Deprecate private _PyUnicodeWriter
  • Documentation
  • Misc changes
Le Déjeuner des Canotiers by Auguste Renoir

Painting: Le Déjeuner des Canotiers (1881) by Auguste Renoir.

gh-93649: Reorganize C API tests

Tests on the C API are written in Python and C. The C part is made of a big file Modules/_testcapimodule.c (4,410 lines) and 37 C files in the Modules/_testcapi/ directory. At the beginning, _testcapimodule.c was the only file and there is a work-in-progress to split it into smaller files.

I moved more codes from _testcapimodule.c into sub-files:

  • Add Modules/_testcapi/frame.c file.
  • Add Modules/_testcapi/type.c file.
  • Add Modules/_testcapi/function.c file.
  • Move _testcapi tests to specific files.

_testcapimodule.c size before/after my changes:

  • Before: 4,410 lines
  • After: 3,375 lines (-1,035 lines: 23% smaller)

gh-129354: Use PyErr_FormatUnraisable()

When an error occurs, Python usually raises an exception to let the developer decides how to handle the error. In some rare cases, exceptions cannot be raised and sys.unraisablehook is called instead.

Before, many of these "unraisable exceptions" were logged with limited or no context. I modified these functions to explain why these errors were logged.

Example of change:

-            PyErr_WriteUnraisable(self);
+            PyErr_FormatUnraisable("Exception ignored "
+                                   "while finalizing file %R", self);

Before, only the self object was logged with a generic error message. Now the "Exception ignored while finalizing file" specific message is logged which explains where the error comes from.

I replaced PyErr_FormatUnraisable() with PyErr_FormatUnraisable() in 20 C files. And I had to update 7 related Python test files.

gh-129539: Reorganize includes

The posixmodule.c file is the biggest C file of the Python project: it is made of 18,206 lines of C code.

It starts with 600 lines of code to include 103 header files. These lines were not well organized leading to a bug (EX_OK symbol).

I reorganized these 600 lines to add sections, group similar includes, and add a comment explaining why each include is needed. For example, the <unistd.h> header is needed to get the symlink() function:

#ifdef HAVE_UNISTD_H
#  include <unistd.h>             // symlink()
#endif

gh-91417, C API: Remove PySequence_Fast()

While digging into open C API issues, I found an old bug (2022) about the PySequence_Fast() function in the limited C API.

The PySequence_Fast() function should be used with PySequence_Fast_GET_SIZE() and PySequence_Fast_GET_ITEM() macros, but these macros don't work in the limited C API.

I decided to remove PySequence_Fast() and these macros from the limited C API. The function never worked with the limited C API. It was added by mistake.

Sadly, one month later, my colleague Karolina Surma discovered that PyQt6 is broken by Python 3.14a5: PyQt6 uses the removed PySequence_Fast()! I'm working on adding the function back.

gh-111178, C API: Fix function signatures

When Python is built with clang -fsanitize=undefined, Python fails quickly on calling functions with the wrong ABI. For example, the tp_dealloc ABI is:

void tp_dealloc(PyObject *self)

whereas the built-in list type used the ABI:

void list_dealloc(PyListObject *op)

PyObject* and PyListObject* are not the same type causing an undefined behavior.

The correct function signature is:

void list_dealloc(PyObject *op)

In February, I fixed the function signature in 4 files:

  • symtable.c
  • namespaceobject.c
  • instruction_sequence.c
  • sliceobject.c

Since October 2023, there is a long on-going work-in-progress to fix all function signatures. It's a lot of work. At the end of February 2025, 97 pull requests have already been merged to fix signatures.

gh-128863, C API: Deprecate private _PyUnicodeWriter

I added a new public PyUnicodeWriter C API to Python 3.14. So I deprecated the old private _PyUnicodeWriter C API:

  • _PyUnicodeWriter_Init()
  • _PyUnicodeWriter_Finish()
  • _PyUnicodeWriter_Dealloc()
  • _PyUnicodeWriter_WriteChar()
  • _PyUnicodeWriter_WriteStr()
  • _PyUnicodeWriter_WriteSubstring()
  • _PyUnicodeWriter_WriteASCIIString()
  • _PyUnicodeWriter_WriteLatin1String()

This deprecation was controversial and has to go through a C API Working Group decision.

Misc changes

  • gh-128911: Use the new PyImport_ImportModuleAttr() function:
    • Replace PyImport_ImportModule() + PyObject_GetAttr() with PyImport_ImportModuleAttr().
    • Replace PyImport_ImportModule() + PyObject_GetAttrString() with PyImport_ImportModuleAttrString().
  • gh-129363: Add colors to tests run in sequentially mode. First, write the test name without color. Then, write the test name and the result with color. Each test is displayed twice.
  • gh-109959: Remove test_glob.test_selflink() test. The test is not reliable, it fails randomly on Linux.