Skip to content

Commit 3e50341

Browse files
committed
gh-120389: Add PyLong_FromInt64() and PyLong_ToInt64()
Add new functions to convert C <stdint.h> numbers from/to Python int: * PyLong_FromInt32() * PyLong_FromUInt32() * PyLong_FromInt64() * PyLong_FromUInt64() * PyLong_ToInt32() * PyLong_ToUInt32() * PyLong_ToInt64() * PyLong_ToUInt64()
1 parent ace2045 commit 3e50341

File tree

12 files changed

+357
-32
lines changed

12 files changed

+357
-32
lines changed

Doc/c-api/long.rst

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,44 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
6969
on failure.
7070
7171
72+
.. c:function:: PyObject* PyLong_FromInt32(int32_t value)
73+
74+
Return a new :c:type:`PyLongObject` object from a signed C
75+
:c:expr:`int32_t`, or ``NULL`` on failure.
76+
77+
.. versionadded:: 3.14
78+
79+
80+
.. c:function:: PyObject* PyLong_FromInt64(int64_t value)
81+
82+
Return a new :c:type:`PyLongObject` object from a signed C
83+
:c:expr:`int64_t`, or ``NULL`` on failure.
84+
85+
.. versionadded:: 3.14
86+
87+
7288
.. c:function:: PyObject* PyLong_FromUnsignedLongLong(unsigned long long v)
7389
7490
Return a new :c:type:`PyLongObject` object from a C :c:expr:`unsigned long long`,
7591
or ``NULL`` on failure.
7692
7793
94+
.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value)
95+
96+
Return a new :c:type:`PyLongObject` object from an unsigned C
97+
:c:expr:`uint32_t`, or ``NULL`` on failure.
98+
99+
.. versionadded:: 3.14
100+
101+
102+
.. c:function:: PyObject* PyLong_FromUInt64(uint64_t value)
103+
104+
Return a new :c:type:`PyLongObject` object from an unsigned C
105+
:c:expr:`uint64_t`, or ``NULL`` on failure.
106+
107+
.. versionadded:: 3.14
108+
109+
78110
.. c:function:: PyObject* PyLong_FromDouble(double v)
79111
80112
Return a new :c:type:`PyLongObject` object from the integer part of *v*, or
@@ -337,6 +369,54 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
337369
This function will no longer use :meth:`~object.__int__`.
338370
339371
372+
.. c:function:: int PyLong_ToInt32(PyObject *obj, int32_t *value)
373+
374+
Return a signed C :c:expr:`int32_t` representation of *obj*.
375+
376+
If the *obj* value is out of range, raise an :exc:`OverflowError`.
377+
378+
Set *\*value* and return ``0`` on success.
379+
Set an exception and return ``-1`` on error.
380+
381+
.. versionadded:: 3.14
382+
383+
384+
.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value)
385+
386+
Return an unsigned C :c:expr:`uint32_t` representation of *obj*.
387+
388+
If the *obj* value is out of range, raise an :exc:`OverflowError`.
389+
390+
Set *\*value* and return ``0`` on success.
391+
Set an exception and return ``-1`` on error.
392+
393+
.. versionadded:: 3.14
394+
395+
396+
.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value)
397+
398+
Return a signed C :c:expr:`int64_t` representation of *obj*.
399+
400+
If the *obj* value is out of range, raise an :exc:`OverflowError`.
401+
402+
Set *\*value* and return ``0`` on success.
403+
Set an exception and return ``-1`` on error.
404+
405+
.. versionadded:: 3.14
406+
407+
408+
.. c:function:: int PyLong_ToUInt64(PyObject *obj, uint64_t *value)
409+
410+
Return an unsigned C :c:expr:`uint64_t` representation of *obj*.
411+
412+
If the *obj* value is out of range, raise an :exc:`OverflowError`.
413+
414+
Set *\*value* and return ``0`` on success.
415+
Set an exception and return ``-1`` on error.
416+
417+
.. versionadded:: 3.14
418+
419+
340420
.. c:function:: double PyLong_AsDouble(PyObject *pylong)
341421
342422
Return a C :c:expr:`double` representation of *pylong*. *pylong* must be

Doc/data/stable_abi.dat

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.14.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,20 @@ New Features
298298

299299
(Contributed by Victor Stinner in :gh:`119182`.)
300300

301+
* Add new functions to convert C ``<stdint.h>`` numbers from/to Python
302+
:class:`int`:
303+
304+
* :c:func:`PyLong_FromInt32`
305+
* :c:func:`PyLong_FromUInt32`
306+
* :c:func:`PyLong_FromInt64`
307+
* :c:func:`PyLong_FromUInt64`
308+
* :c:func:`PyLong_ToInt32`
309+
* :c:func:`PyLong_ToUInt32`
310+
* :c:func:`PyLong_ToInt64`
311+
* :c:func:`PyLong_ToUInt64`
312+
313+
(Contributed by Victor Stinner in :gh:`120389`.)
314+
301315
Porting to Python 3.14
302316
----------------------
303317

Include/longobject.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLongMask(PyObject *);
3030
PyAPI_FUNC(int) PyLong_AsInt(PyObject *);
3131
#endif
3232

33+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
34+
PyAPI_FUNC(PyObject*) PyLong_FromInt32(int32_t value);
35+
PyAPI_FUNC(PyObject*) PyLong_FromUInt32(uint32_t value);
36+
PyAPI_FUNC(PyObject*) PyLong_FromInt64(int64_t value);
37+
PyAPI_FUNC(PyObject*) PyLong_FromUInt64(uint64_t value);
38+
39+
PyAPI_FUNC(int) PyLong_ToInt32(PyObject *obj, int32_t *value);
40+
PyAPI_FUNC(int) PyLong_ToUInt32(PyObject *obj, uint32_t *value);
41+
PyAPI_FUNC(int) PyLong_ToInt64(PyObject *obj, int64_t *value);
42+
PyAPI_FUNC(int) PyLong_ToUInt64(PyObject *obj, uint64_t *value);
43+
#endif
44+
3345
PyAPI_FUNC(PyObject *) PyLong_GetInfo(void);
3446

3547
/* It may be useful in the future. I've added it in the PyInt -> PyLong

Lib/test/test_capi/test_long.py

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -185,25 +185,28 @@ def test_long_asint(self):
185185
self.assertRaises(TypeError, PyLong_AsInt, '3')
186186
self.assertRaises(SystemError, PyLong_AsInt, NULL)
187187

188+
def check_long_asint(self, long_asint, min_val, max_val):
189+
# round trip (object -> C integer -> object)
190+
for value in (min_val, max_val, -1, 0, 1, 1234):
191+
with self.subTest(value=value):
192+
self.assertEqual(long_asint(value), value)
193+
194+
self.assertEqual(long_asint(IntSubclass(42)), 42)
195+
self.assertEqual(long_asint(Index(42)), 42)
196+
self.assertEqual(long_asint(MyIndexAndInt()), 10)
197+
198+
self.assertRaises(OverflowError, long_asint, min_val - 1)
199+
self.assertRaises(OverflowError, long_asint, max_val + 1)
200+
self.assertRaises(TypeError, long_asint, 1.0)
201+
self.assertRaises(TypeError, long_asint, b'2')
202+
self.assertRaises(TypeError, long_asint, '3')
203+
self.assertRaises(SystemError, long_asint, NULL)
204+
188205
def test_long_aslong(self):
189206
# Test PyLong_AsLong() and PyLong_FromLong()
190207
aslong = _testlimitedcapi.pylong_aslong
191208
from _testcapi import LONG_MIN, LONG_MAX
192-
# round trip (object -> long -> object)
193-
for value in (LONG_MIN, LONG_MAX, -1, 0, 1, 1234):
194-
with self.subTest(value=value):
195-
self.assertEqual(aslong(value), value)
196-
197-
self.assertEqual(aslong(IntSubclass(42)), 42)
198-
self.assertEqual(aslong(Index(42)), 42)
199-
self.assertEqual(aslong(MyIndexAndInt()), 10)
200-
201-
self.assertRaises(OverflowError, aslong, LONG_MIN - 1)
202-
self.assertRaises(OverflowError, aslong, LONG_MAX + 1)
203-
self.assertRaises(TypeError, aslong, 1.0)
204-
self.assertRaises(TypeError, aslong, b'2')
205-
self.assertRaises(TypeError, aslong, '3')
206-
self.assertRaises(SystemError, aslong, NULL)
209+
self.check_long_asint(aslong, LONG_MIN, LONG_MAX)
207210

208211
def test_long_aslongandoverflow(self):
209212
# Test PyLong_AsLongAndOverflow()
@@ -223,25 +226,28 @@ def test_long_aslongandoverflow(self):
223226
# CRASHES aslongandoverflow(1.0)
224227
# CRASHES aslongandoverflow(NULL)
225228

226-
def test_long_asunsignedlong(self):
227-
# Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()
228-
asunsignedlong = _testlimitedcapi.pylong_asunsignedlong
229-
from _testcapi import ULONG_MAX
229+
def check_long_asunsignedint(self, long_asuint, max_val):
230230
# round trip (object -> unsigned long -> object)
231-
for value in (ULONG_MAX, 0, 1, 1234):
231+
for value in (0, 1, 1234, max_val):
232232
with self.subTest(value=value):
233-
self.assertEqual(asunsignedlong(value), value)
233+
self.assertEqual(long_asuint(value), value)
234+
235+
self.assertEqual(long_asuint(IntSubclass(42)), 42)
236+
self.assertRaises(TypeError, long_asuint, Index(42))
237+
self.assertRaises(TypeError, long_asuint, MyIndexAndInt())
234238

235-
self.assertEqual(asunsignedlong(IntSubclass(42)), 42)
236-
self.assertRaises(TypeError, asunsignedlong, Index(42))
237-
self.assertRaises(TypeError, asunsignedlong, MyIndexAndInt())
239+
self.assertRaises(OverflowError, long_asuint, -1)
240+
self.assertRaises(OverflowError, long_asuint, max_val + 1)
241+
self.assertRaises(TypeError, long_asuint, 1.0)
242+
self.assertRaises(TypeError, long_asuint, b'2')
243+
self.assertRaises(TypeError, long_asuint, '3')
244+
self.assertRaises(SystemError, long_asuint, NULL)
238245

239-
self.assertRaises(OverflowError, asunsignedlong, -1)
240-
self.assertRaises(OverflowError, asunsignedlong, ULONG_MAX + 1)
241-
self.assertRaises(TypeError, asunsignedlong, 1.0)
242-
self.assertRaises(TypeError, asunsignedlong, b'2')
243-
self.assertRaises(TypeError, asunsignedlong, '3')
244-
self.assertRaises(SystemError, asunsignedlong, NULL)
246+
def test_long_asunsignedlong(self):
247+
# Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()
248+
asunsignedlong = _testlimitedcapi.pylong_asunsignedlong
249+
from _testcapi import ULONG_MAX
250+
self.check_long_asunsignedint(asunsignedlong, ULONG_MAX)
245251

246252
def test_long_asunsignedlongmask(self):
247253
# Test PyLong_AsUnsignedLongMask()
@@ -737,6 +743,29 @@ def test_long_getsign(self):
737743

738744
# CRASHES getsign(NULL)
739745

746+
def test_long_asint32(self):
747+
# Test PyLong_ToInt32() and PyLong_FromInt32()
748+
to_int32 = _testlimitedcapi.pylong_toint32
749+
from _testcapi import INT32_MIN, INT32_MAX
750+
self.check_long_asint(to_int32, INT32_MIN, INT32_MAX)
751+
752+
def test_long_asuint32(self):
753+
# Test PyLong_ToUInt32() and PyLong_FromUInt32()
754+
to_uint32 = _testlimitedcapi.pylong_touint32
755+
from _testcapi import UINT32_MAX
756+
self.check_long_asunsignedint(to_uint32, UINT32_MAX)
757+
758+
def test_long_asint64(self):
759+
# Test PyLong_ToInt64() and PyLong_FromInt64()
760+
to_int64 = _testlimitedcapi.pylong_toint64
761+
from _testcapi import INT64_MIN, INT64_MAX
762+
self.check_long_asint(to_int64, INT64_MIN, INT64_MAX)
763+
764+
def test_long_asuint64(self):
765+
# Test PyLong_ToUInt64() and PyLong_FromUInt64()
766+
to_uint64 = _testlimitedcapi.pylong_touint64
767+
from _testcapi import UINT64_MAX
768+
self.check_long_asunsignedint(to_uint64, UINT64_MAX)
740769

741770
if __name__ == "__main__":
742771
unittest.main()

Lib/test/test_stable_abi_ctypes.py

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Add new functions to convert C ``<stdint.h>`` numbers from/to Python
2+
:class:`int`:
3+
4+
* :c:func:`PyLong_FromInt32`
5+
* :c:func:`PyLong_FromUInt32`
6+
* :c:func:`PyLong_FromInt64`
7+
* :c:func:`PyLong_FromUInt64`
8+
* :c:func:`PyLong_ToInt32`
9+
* :c:func:`PyLong_ToUInt32`
10+
* :c:func:`PyLong_ToInt64`
11+
* :c:func:`PyLong_ToUInt64`
12+
13+
Patch by Victor Stinner.

Misc/stable_abi.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2510,3 +2510,19 @@
25102510

25112511
[function.Py_TYPE]
25122512
added = '3.14'
2513+
[function.PyLong_FromInt32]
2514+
added = '3.14'
2515+
[function.PyLong_FromUInt32]
2516+
added = '3.14'
2517+
[function.PyLong_ToInt32]
2518+
added = '3.14'
2519+
[function.PyLong_ToUInt32]
2520+
added = '3.14'
2521+
[function.PyLong_FromInt64]
2522+
added = '3.14'
2523+
[function.PyLong_FromUInt64]
2524+
added = '3.14'
2525+
[function.PyLong_ToInt64]
2526+
added = '3.14'
2527+
[function.PyLong_ToUInt64]
2528+
added = '3.14'

Modules/_testcapimodule.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4047,6 +4047,12 @@ PyInit__testcapi(void)
40474047

40484048
PyModule_AddIntConstant(m, "the_number_three", 3);
40494049
PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT);
4050+
PyModule_AddObject(m, "INT32_MIN", PyLong_FromInt32(INT32_MIN));
4051+
PyModule_AddObject(m, "INT32_MAX", PyLong_FromInt32(INT32_MAX));
4052+
PyModule_AddObject(m, "UINT32_MAX", PyLong_FromUInt32(UINT32_MAX));
4053+
PyModule_AddObject(m, "INT64_MIN", PyLong_FromInt64(INT64_MIN));
4054+
PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX));
4055+
PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX));
40504056

40514057
if (PyModule_AddIntMacro(m, Py_single_input)) {
40524058
return NULL;

0 commit comments

Comments
 (0)