Skip to content

Commit 659cfe5

Browse files
authored
PEP 757: address review, sync with PR and prettify (#3975)
1 parent 9475fa0 commit 659cfe5

File tree

2 files changed

+120
-117
lines changed

2 files changed

+120
-117
lines changed

peps/conf.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@
5252
# Warn on missing references
5353
nitpicky = True
5454

55+
nitpick_ignore = [
56+
# Standard C types
57+
("c:type", "int8_t"),
58+
("c:type", "uint8_t"),
59+
("c:type", "int64_t"),
60+
]
61+
for role, name in list(nitpick_ignore):
62+
if role in ("c:type", "c:struct"):
63+
nitpick_ignore.append(("c:identifier", name))
64+
del role, name
65+
5566
# Intersphinx configuration
5667
intersphinx_mapping = {
5768
"python": ("https://docs.python.org/3/", None),

peps/pep-0757.rst

Lines changed: 109 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Abstract
1717
========
1818

1919
Add a new C API to import and export Python integers, :class:`int` objects:
20-
especially ``PyLongWriter_Create()`` and ``PyLong_Export()`` functions.
20+
especially :c:func:`PyLongWriter_Create()` and :c:func:`PyLong_Export()` functions.
2121

2222

2323
Rationale
@@ -26,12 +26,12 @@ Rationale
2626
Projects such as `gmpy2 <https://github.com/aleaxit/gmpy>`_, `SAGE
2727
<https://www.sagemath.org/>`_ and `Python-FLINT
2828
<https://github.com/flintlib/python-flint>`_ access directly Python
29-
"internals" (the ``PyLongObject`` structure) or use an inefficient
29+
"internals" (the :c:type:`PyLongObject` structure) or use an inefficient
3030
temporary format (hex strings for Python-FLINT) to import and
3131
export Python :class:`int` objects. The Python :class:`int` implementation
3232
changed in Python 3.12 to add a tag and "compact values".
3333

34-
In the 3.13 alpha 1 release, the private undocumented ``_PyLong_New()``
34+
In the 3.13 alpha 1 release, the private undocumented :c:func:`!_PyLong_New()`
3535
function had been removed, but it is being used by these projects to
3636
import Python integers. The private function has been restored in 3.13
3737
alpha 2.
@@ -49,38 +49,46 @@ Specification
4949
Layout API
5050
----------
5151

52-
API::
52+
Data needed by `GMP <https://gmplib.org/>`_-like import-export functions.
5353

54-
typedef struct PyLongLayout {
55-
// Bits per digit
56-
uint8_t bits_per_digit;
54+
.. c:struct:: PyLongLayout
5755
58-
// Digit size in bytes
59-
uint8_t digit_size;
56+
Layout of an array of digits, used by Python :class:`int` object.
6057

61-
// Digits order:
62-
// * 1 for most significant digit first
63-
// * -1 for least significant digit first
64-
int8_t digits_order;
58+
Use :c:func:`PyLong_GetNativeLayout` to get the native layout of Python
59+
:class:`int` objects.
6560

66-
// Endianness:
67-
// * 1 for most significant byte first (big endian)
68-
// * -1 for least significant byte first (little endian)
69-
int8_t endianness;
70-
} PyLongLayout;
61+
See also :data:`sys.int_info` which exposes similar information to Python.
7162

72-
PyAPI_FUNC(const PyLongLayout*) PyLong_GetNativeLayout(void);
63+
.. c:member:: uint8_t bits_per_digit
7364
74-
Data needed by `GMP <https://gmplib.org/>`_-like import-export functions.
65+
Bits per digit.
66+
67+
.. c:member:: uint8_t digit_size
68+
69+
Digit size in bytes.
70+
71+
.. c:member:: int8_t digits_order
72+
73+
Digits order:
74+
75+
- ``1`` for most significant digit first
76+
- ``-1`` for least significant digit first
7577

76-
PyLong_GetNativeLayout()
77-
^^^^^^^^^^^^^^^^^^^^^^^^
78+
.. c:member:: int8_t endian
7879
79-
API::
80+
Digit endianness:
8081

81-
const PyLongLayout* PyLong_GetNativeLayout(void)
82+
- ``1`` for most significant byte first (big endian)
83+
- ``-1`` for least significant first (little endian)
84+
85+
86+
.. c:function:: const PyLongLayout* PyLong_GetNativeLayout(void)
87+
88+
Get the native layout of Python :class:`int` objects.
89+
90+
See the :c:struct:`PyLongLayout` structure.
8291
83-
Get the native layout of Python :class:`int` objects.
8492
8593
The function must not be called before Python initialization nor after
8694
Python finalization. The returned layout is valid until Python is
@@ -91,134 +99,112 @@ so it can be cached.
9199
Export API
92100
----------
93101
94-
Export a Python integer as a digits array::
102+
.. c:struct:: PyLongExport
95103
96-
typedef struct PyLongExport {
97-
// use value, if digits set to NULL.
98-
int64_t value;
104+
Export of a Python :class:`int` object.
99105
100-
// 1 if the number is negative, 0 otherwise.
101-
uint8_t negative;
106+
There are two cases:
102107
103-
// Number of digits in the 'digits' array.
104-
Py_ssize_t ndigits;
108+
* If :c:member:`digits` is ``NULL``, only use the :c:member:`value` member.
109+
Calling :c:func:`PyLong_FreeExport` is optional in this case.
110+
* If :c:member:`digits` is not ``NULL``, use :c:member:`negative`,
111+
:c:member:`ndigits` and :c:member:`digits` members.
112+
Calling :c:func:`PyLong_FreeExport` is mandatory in this case.
105113
106-
// Read-only array of unsigned digits.
107-
const void *digits;
114+
.. c:member:: int64_t value
108115
109-
// Member used internally, must not be used for other purpose.
110-
Py_uintptr_t _reserved;
111-
} PyLongExport;
116+
The native integer value of the exported :class:`int` object.
117+
Only valid if :c:member:`digits` is ``NULL``.
112118
113-
int PyLong_Export(PyObject *obj, PyLongExport *array);
114-
void PyLong_FreeExport(PyLongExport *array);
119+
.. c:member:: uint8_t negative
115120
116-
On CPython 3.14, no memory copy is needed, it's just a thin wrapper to
117-
expose Python int internal digits array.
121+
1 if the number is negative, 0 otherwise.
122+
Only valid if :c:member:`digits` is not ``NULL``.
118123
119-
``PyLongExport._reserved``, if ``digits`` not ``NULL``, stores a strong
120-
reference to the Python :class:`int` object to make sure that that structure
121-
remains valid until ``PyLong_FreeExport()`` is called.
124+
.. c:member:: Py_ssize_t ndigits
122125
126+
Number of digits in :c:member:`digits` array.
127+
Only valid if :c:member:`digits` is not ``NULL``.
123128
124-
PyLong_Export()
125-
^^^^^^^^^^^^^^^
129+
.. c:member:: const void *digits
126130
127-
API::
131+
Read-only array of unsigned digits. Can be ``NULL``.
128132
129-
int PyLong_Export(PyObject *obj, PyLongExport *array)
130133
131-
Export a Python :class:`int` object as a digits array.
134+
.. c:function:: int PyLong_Export(PyObject *obj, PyLongExport *export_long)
132135
133-
On success, set *\*array* and return 0.
134-
On error, set an exception and return -1.
136+
Export a Python :class:`int` object.
135137
136-
If ``array->digits`` set to ``NULL``, caller must use instead ``array->value``
137-
to get value of an :class:`int` object.
138+
On success, set *\*export_long* and return 0.
139+
On error, set an exception and return -1.
138140
139-
CPython implementation detail: This function always succeeds if *obj* is a
140-
Python :class:`int` object or a subclass.
141+
This function always succeeds if *obj* is a Python :class:`int` object or a
142+
subclass.
141143
142-
``PyLong_FreeExport()`` must be called once done with using *array*.
144+
If *export_long.digits* is not ``NULL``, :c:func:`PyLong_FreeExport` must be
145+
called when the export is no longer needed.
143146
147+
On CPython 3.14, no memory copy is needed, it's just a thin wrapper to
148+
expose Python int internal digits array.
144149
145-
PyLong_FreeExport()
146-
^^^^^^^^^^^^^^^^^^^
150+
A private field of the :c:struct:`PyLongExport`, if
151+
:c:member:`~PyLongExport.digits` not ``NULL``, stores a strong reference to the
152+
Python :class:`int` object to make sure that that structure remains valid until
153+
:c:func:`PyLong_FreeExport()` is called.
147154
148-
API::
149155
150-
void PyLong_FreeExport(PyLongExport *array)
156+
.. c:function:: void PyLong_FreeExport(PyLongExport *export_long)
151157
152-
Free the export *array* created by ``PyLong_Export()``.
158+
Release the export *export_long* created by :c:func:`PyLong_Export`.
153159
154160
155161
Import API
156162
----------
157163
158-
Import a Python integer from a digits array::
159-
160-
// A Python integer writer instance.
161-
// The instance must be destroyed by PyLongWriter_Finish().
162-
typedef struct PyLongWriter PyLongWriter;
163-
164-
PyAPI_FUNC(PyLongWriter*) PyLongWriter_Create(
165-
int negative,
166-
Py_ssize_t ndigits,
167-
void **digits);
168-
PyAPI_FUNC(PyObject*) PyLongWriter_Finish(PyLongWriter *writer);
169-
PyAPI_FUNC(void) PyLongWriter_Discard(PyLongWriter *writer);
164+
The :c:type:`PyLongWriter` API can be used to import an integer.
170165
171-
On CPython 3.14, the implementation is a thin wrapper to the private
172-
``_PyLong_New()`` function.
173-
174-
``PyLongWriter_Finish()`` takes care of normalizing the digits and
175-
converts the object to a compact integer if needed.
166+
.. c:struct:: PyLongWriter
176167
168+
A Python :class:`int` writer instance.
177169
178-
PyLongWriter_Create()
179-
^^^^^^^^^^^^^^^^^^^^^
170+
The instance must be destroyed by :c:func:`PyLongWriter_Finish`.
180171
181-
API::
182172
183-
PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
173+
.. c:function:: PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
184174
185-
Create a ``PyLongWriter``.
175+
Create a :c:type:`PyLongWriter`.
186176
187-
On success, set *\*digits* and return a writer.
188-
On error, set an exception and return ``NULL``.
177+
On success, set *\*digits* and return a writer.
178+
On error, set an exception and return ``NULL``.
189179
190-
*negative* is ``1`` if the number is negative, or ``0`` otherwise.
180+
*negative* is ``1`` if the number is negative, or ``0`` otherwise.
191181
192-
*ndigits* is the number of digits in the *digits* array. It must be
193-
greater than or equal to 0.
182+
*ndigits* is the number of digits in the *digits* array. It must be
183+
greater than or equal to 0.
194184
195-
The caller must initialize the digits array *digits* and then call
196-
``PyLongWriter_Finish()`` to get a Python :class:`int`. Digits must be
197-
in the range [``0``; ``PyLong_BASE - 1``]. Unused digits must be set to
198-
``0``.
185+
The caller must initialize the array of digits *digits* and then call
186+
:c:func:`PyLongWriter_Finish` to get a Python :class:`int`. Digits must be
187+
in the range [``0``; ``PyLong_BASE - 1``]. Unused digits must be set to
188+
``0``.
199189
190+
On CPython 3.14, the implementation is a thin wrapper to the private
191+
:c:func:`!_PyLong_New()` function.
200192
201-
PyLongWriter_Finish()
202-
^^^^^^^^^^^^^^^^^^^^^
203-
204-
API::
205-
206-
PyObject* PyLongWriter_Finish(PyLongWriter *writer)
207193
208-
Finish a ``PyLongWriter`` created by ``PyLongWriter_Create()``.
194+
.. c:function:: PyObject* PyLongWriter_Finish(PyLongWriter *writer)
209195
210-
On success, return a Python :class:`int` object.
211-
On error, set an exception and return ``NULL``.
196+
Finish a :c:type:`PyLongWriter` created by :c:func:`PyLongWriter_Create`.
212197
198+
On success, return a Python :class:`int` object.
199+
On error, set an exception and return ``NULL``.
213200
214-
PyLongWriter_Discard()
215-
^^^^^^^^^^^^^^^^^^^^^^
201+
:c:func:`PyLongWriter_Finish()` takes care of normalizing the digits and
202+
converts the object to a compact integer if needed.
216203
217-
API::
218204
219-
void PyLongWriter_Discard(PyLongWriter *writer)
205+
.. c:function:: void PyLongWriter_Discard(PyLongWriter *writer)
220206
221-
Discard the internal object and destroy the writer instance.
207+
Discard the internal object and destroy the writer instance.
222208
223209
224210
Optimize import for small integers
@@ -252,8 +238,8 @@ Implementation
252238
Benchmarks
253239
==========
254240
255-
Export: PyLong_Export() with gmpy2
256-
----------------------------------
241+
Export: :c:func:`PyLong_Export()` with gmpy2
242+
--------------------------------------------
257243
258244
Code::
259245
@@ -284,14 +270,17 @@ Code::
284270
if (long_export.value < 0) {
285271
mpz_t tmp;
286272
mpz_init(tmp);
287-
mpz_ui_pow_ui(tmp, 2, 8*sizeof(size_t));
273+
mpz_ui_pow_ui(tmp, 2, 64);
288274
mpz_sub(z, z, tmp);
289275
mpz_clear(tmp);
290276
}
291277
}
292278
}
293279
}
294280
281+
Reference code: `mpz_set_PyLong() in the gmpy2 master for commit 9177648
282+
<https://github.com/aleaxit/gmpy/blob/9177648c23f5c507e46b81c1eb7d527c79c96f00/src/gmpy2_convert_gmp.c#L42-L69>`_.
283+
295284
Benchmark:
296285
297286
.. code-block:: py
@@ -323,8 +312,8 @@ mode:
323312
+----------------+---------+-----------------------+
324313
325314
326-
Import: PyLongWriter_Create() with gmpy2
327-
----------------------------------------
315+
Import: :c:func:`PyLongWriter_Create()` with gmpy2
316+
--------------------------------------------------
328317
329318
Code::
330319
@@ -353,6 +342,9 @@ Code::
353342
return PyLongWriter_Finish(writer);
354343
}
355344
345+
Reference code: `GMPy_PyLong_From_MPZ() in the gmpy2 master for commit 9177648
346+
<https://github.com/aleaxit/gmpy/blob/9177648c23f5c507e46b81c1eb7d527c79c96f00/src/gmpy2_convert_gmp.c#L128-L156>`_.
347+
356348
Benchmark:
357349
358350
.. code-block:: py
@@ -393,16 +385,16 @@ Open Questions
393385
==============
394386
395387
* Should we add *digits_order* and *endianness* members to :data:`sys.int_info`
396-
and remove ``PyLong_GetNativeLayout()``? The
397-
``PyLong_GetNativeLayout()`` function returns a C structure
388+
and remove :c:func:`PyLong_GetNativeLayout()`? The
389+
:c:func:`PyLong_GetNativeLayout()` function returns a C structure
398390
which is more convenient to use in C than :data:`sys.int_info` which uses
399391
Python objects.
400392
* Currenly, all required information for :class:`int` import/export is
401393
already available via :c:func:`PyLong_GetInfo()` or :data:`sys.int_info`.
402394
Native endianness of "digits" and current order of digits (least
403395
significant digit first) --- is a common denominator of all libraries
404396
for aribitrary precision integer arithmetic. So, shouldn't we just remove
405-
from API both ``PyLongLayout`` and ``PyLong_GetNativeLayout()`` (which
397+
from API both :c:struct:`PyLongLayout` and :c:func:`PyLong_GetNativeLayout()` (which
406398
is actually just a minor convenience)?
407399
408400
@@ -416,8 +408,8 @@ It would be convenient to support arbitrary layout to import-export
416408
Python integers.
417409
418410
For example, it was proposed to add a *layout* parameter to
419-
``PyLongWriter_Create()`` and a *layout* member to the
420-
``PyLongExport`` structure.
411+
:c:func:`PyLongWriter_Create()` and a *layout* member to the
412+
:c:struct:`PyLongExport` structure.
421413
422414
The problem is that it's more complex to implement and not really
423415
needed. What's strictly needed is only an API to import-export using the

0 commit comments

Comments
 (0)