# Choice of complex buffer protocol format intentional break with PEP?

**URL:** <https://discuss.python.org/t/choice-of-complex-buffer-protocol-format-intentional-break-with-pep/106949>\
**Category:** Core Development\
**Created:** [April 13, 2026, 6:54am UTC](https://discuss.python.org/t/choice-of-complex-buffer-protocol-format-intentional-break-with-pep/106949 "2026-04-13T06:54:42Z")\
**Posts on this page:** 1\
**Showing post:** 5

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [April 13, 2026, 12:46pm UTC](https://discuss.python.org/t/choice-of-complex-buffer-protocol-format-intentional-break-with-pep/106949/5 "2026-04-13T12:46:48Z")

</div>

Hi Sebastian,

Thanks for raising this issue here.

> [@seberg](#):
>
> It was [reported to NumPy](https://github.com/numpy/numpy/issues/31204) that the newly added buffer protocol support for complex `array.array` and `ctypes` uses the character codes `D` and `F` rather than `Zd` and `Zf`.

`D` (double complex) and `F` (float complex) format were introduced in Python 3.14 which was released on October 2025, so IMO it’s too late to change the chosen format strings.

- Python 3.14 added `D` and `F` support to the `ctypes` and `struct` module
- Python 3.15 adds `D` and `F` support to the `array` module and to `memoryview`

Before the 3.14 final release, `ctypes` and `struct` used `C` (double complex) and `E` (float complex) formats. These formats were replaced with `D` and `F` [for **compatibility with numpy**](https://github.com/python/cpython/pull/132827):

```python
$ python3.14
>>> import numpy as np
>>> np. __version__'2.3.5'

>>> np.typename("F")
'complex single precision'
>>> np.typename("D")
'complex double precision'

>>> np.dtype(np.complex64).char
'F'
>>> np.dtype(np.complex128).char
'D'

>>> np.array([1, 2, 3], dtype='F')
array([1.+0.j, 2.+0.j, 3.+0.j], dtype=complex64)
>>> np.array([1, 2, 3], dtype='D')
array([1.+0.j, 2.+0.j, 3.+0.j])

>>> memoryview(np.array([1, 2, 3], dtype='F')).format
'Zf'
>>> memoryview(np.array([1, 2, 3], dtype='D')).format
'Zd'

```

> [@seberg](#):
>
> NumPy (and that means _all_ meaningful users of the buffer protocol) would have to keep exporting `Zd` (and Python doesn’t understand this!).

As Sergey wrote above, in the [bfloat16 format discussion](https://discuss.python.org/t/add-bfloat16-support-to-struct/106723), Serhiy proposed adding an API to register custom types:

> [@Add bfloat16 support to struct](https://discuss.python.org/t/add-bfloat16-support-to-struct/106723/67):
>
> For perfomance, we need the C API for registering custom types, but for convenience, we need also the Python API.

It may be a solution to `D` vs `Zd` formats, maybe both can be accepted transparently.

> [@seberg](#):
>
> Now, it’s a fair choice to accept the friction that this introduces, but for now it is still unclear that this was considered much and I think it is a violation of the [PEP](https://peps.python.org/pep-3118/#additions-to-the-struct-string-syntax).

PEP 3118 was written in 2006. It now has the header: “This PEP is a historical document”. I don’t think that using this old PEP is still relevant nowadays. Maybe numpy should reconsider its [error message on unknown format](https://github.com/numpy/numpy/issues/31204#issue-4241560668).

Yeah, it’s unfortunate that numpy and Python stdlib (array, ctypes, struct and memoryview) are using different formats. But it’s maybe the opportunity to provide a generic solution to this kind of problem (API to register formats).

It’s also unfortunate that this issue wasn’t raised before, but as I wrote above, IMO it’s too late to change Python stdlib. By the way, currently the stdlib only use single letter formats which make some parts of the code simpler.

---

_[View the full topic](https://discuss.python.org/t/choice-of-complex-buffer-protocol-format-intentional-break-with-pep/106949)._
