Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-120389: Add PyLong_FromInt64() and PyLong_AsInt64() #120390

Merged
merged 12 commits into from
Aug 28, 2024
55 changes: 55 additions & 0 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,30 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
on failure.


.. c:function:: PyObject* PyLong_FromInt32(int32_t value)
PyObject* PyLong_FromInt64(int64_t value)

Return a new :c:type:`PyLongObject` object from a signed C
:c:expr:`int32_t` or :c:expr:`int64_t`, or ``NULL`` on failure.
vstinner marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 3.14


.. c:function:: PyObject* PyLong_FromUnsignedLongLong(unsigned long long v)

Return a new :c:type:`PyLongObject` object from a C :c:expr:`unsigned long long`,
or ``NULL`` on failure.


.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value)
PyObject* PyLong_FromUInt64(uint64_t value)

Return a new :c:type:`PyLongObject` object from an unsigned C
:c:expr:`uint32_t` or :c:expr:`uint64_t`, or ``NULL`` on failure.
vstinner marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 3.14


.. c:function:: PyObject* PyLong_FromDouble(double v)

Return a new :c:type:`PyLongObject` object from the integer part of *v*, or
Expand Down Expand Up @@ -337,6 +355,43 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
This function will no longer use :meth:`~object.__int__`.


.. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *value)
int PyLong_AsInt64(PyObject *obj, int64_t *value)

Set *\*value* to a signed C :c:expr:`int32_t` or :c:expr:`int64_t`
representation of *obj*.

If the *obj* value is out of range, raise an :exc:`OverflowError`.

Set *\*value* and return ``0`` on success.
Set an exception and return ``-1`` on error.

*value* must not be ``NULL``.

.. versionadded:: 3.14


.. c:function:: int PyLong_AsUInt32(PyObject *obj, uint32_t *value)
int PyLong_AsUInt64(PyObject *obj, uint64_t *value)

Set *\*value* to an unsigned C :c:expr:`uint32_t` or :c:expr:`uint64_t`
representation of *obj*.

If *obj* is not an instance of :c:type:`PyLongObject`, first call its
:meth:`~object.__index__` method (if present) to convert it to a
:c:type:`PyLongObject`.

* If *obj* is negative, raise a :exc:`ValueError`.
* If the *obj* value is out of range, raise an :exc:`OverflowError`.

Set *\*value* and return ``0`` on success.
Set an exception and return ``-1`` on error.

*value* must not be ``NULL``.

.. versionadded:: 3.14


.. c:function:: double PyLong_AsDouble(PyObject *pylong)

Return a C :c:expr:`double` representation of *pylong*. *pylong* must be
Expand Down
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
('c:type', 'size_t'),
('c:type', 'ssize_t'),
('c:type', 'time_t'),
('c:type', 'uint32_t'),
('c:type', 'uint64_t'),
('c:type', 'uintmax_t'),
('c:type', 'uintptr_t'),
Expand Down
8 changes: 8 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,20 @@ New Features
an interned string and deallocate it during module shutdown.
(Contribued by Eddie Elizondo in :gh:`113601`.)

* Add new functions to convert C ``<stdint.h>`` numbers from/to Python
:class:`int`:

* :c:func:`PyLong_FromInt32`
* :c:func:`PyLong_FromInt64`
* :c:func:`PyLong_FromUInt32`
* :c:func:`PyLong_FromUInt64`
* :c:func:`PyLong_AsInt32`
* :c:func:`PyLong_AsInt64`
* :c:func:`PyLong_AsUInt32`
* :c:func:`PyLong_AsUInt64`

(Contributed by Victor Stinner in :gh:`120389`.)

Porting to Python 3.14
----------------------

Expand Down
12 changes: 12 additions & 0 deletions Include/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLongMask(PyObject *);
PyAPI_FUNC(int) PyLong_AsInt(PyObject *);
#endif

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
PyAPI_FUNC(PyObject*) PyLong_FromInt32(int32_t value);
PyAPI_FUNC(PyObject*) PyLong_FromUInt32(uint32_t value);
PyAPI_FUNC(PyObject*) PyLong_FromInt64(int64_t value);
PyAPI_FUNC(PyObject*) PyLong_FromUInt64(uint64_t value);

PyAPI_FUNC(int) PyLong_AsInt32(PyObject *obj, int32_t *value);
PyAPI_FUNC(int) PyLong_AsUInt32(PyObject *obj, uint32_t *value);
PyAPI_FUNC(int) PyLong_AsInt64(PyObject *obj, int64_t *value);
PyAPI_FUNC(int) PyLong_AsUInt64(PyObject *obj, uint64_t *value);
#endif

PyAPI_FUNC(PyObject *) PyLong_GetInfo(void);

/* It may be useful in the future. I've added it in the PyInt -> PyLong
Expand Down
49 changes: 49 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,30 @@ def test_long_aslongandoverflow(self):
from _testcapi import LONG_MIN, LONG_MAX
self.check_long_asintandoverflow(aslongandoverflow, LONG_MIN, LONG_MAX)

def check_long_asunsignedint(self, long_asuint, max_val,
use_index=False, negative_value_error=False):
# round trip (object -> unsigned long -> object)
for value in (0, 1, 1234, max_val):
with self.subTest(value=value):
self.assertEqual(long_asuint(value), value)
if use_index:
self.assertEqual(long_asuint(Index(value)), value)

self.assertEqual(long_asuint(IntSubclass(42)), 42)
if not use_index:
self.assertRaises(TypeError, long_asuint, Index(42))
self.assertRaises(TypeError, long_asuint, MyIndexAndInt())

if negative_value_error:
self.assertRaises(ValueError, long_asuint, -1)
else:
self.assertRaises(OverflowError, long_asuint, -1)
self.assertRaises(OverflowError, long_asuint, max_val + 1)
self.assertRaises(TypeError, long_asuint, 1.0)
self.assertRaises(TypeError, long_asuint, b'2')
self.assertRaises(TypeError, long_asuint, '3')
self.assertRaises(SystemError, long_asuint, NULL)

def test_long_asunsignedlong(self):
# Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()
asunsignedlong = _testlimitedcapi.pylong_asunsignedlong
Expand Down Expand Up @@ -631,6 +655,31 @@ def test_long_getsign(self):

# CRASHES getsign(NULL)

def test_long_asint32(self):
# Test PyLong_AsInt32() and PyLong_FromInt32()
to_int32 = _testlimitedcapi.pylong_asint32
from _testcapi import INT32_MIN, INT32_MAX
self.check_long_asint(to_int32, INT32_MIN, INT32_MAX)

def test_long_asint64(self):
# Test PyLong_AsInt64() and PyLong_FromInt64()
as_int64 = _testlimitedcapi.pylong_asint64
from _testcapi import INT64_MIN, INT64_MAX
self.check_long_asint(as_int64, INT64_MIN, INT64_MAX)

def test_long_asuint32(self):
# Test PyLong_AsUInt32() and PyLong_FromUInt32()
as_uint32 = _testlimitedcapi.pylong_asuint32
from _testcapi import UINT32_MAX
self.check_long_asunsignedint(as_uint32, UINT32_MAX, use_index=True,
negative_value_error=True)
vstinner marked this conversation as resolved.
Show resolved Hide resolved

def test_long_asuint64(self):
# Test PyLong_AsUInt64() and PyLong_FromUInt64()
as_uint64 = _testlimitedcapi.pylong_asuint64
from _testcapi import UINT64_MAX
self.check_long_asunsignedint(as_uint64, UINT64_MAX, use_index=True,
negative_value_error=True)

if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Add new functions to convert C ``<stdint.h>`` numbers from/to Python
:class:`int`:

* :c:func:`PyLong_FromInt32`
* :c:func:`PyLong_FromUInt32`
* :c:func:`PyLong_FromInt64`
* :c:func:`PyLong_FromUInt64`
* :c:func:`PyLong_AsInt32`
* :c:func:`PyLong_AsUInt32`
* :c:func:`PyLong_AsInt64`
* :c:func:`PyLong_AsUInt64`

Patch by Victor Stinner.
16 changes: 16 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2510,3 +2510,19 @@
added = '3.14'
[function.PyIter_NextItem]
added = '3.14'
[function.PyLong_FromInt32]
added = '3.14'
[function.PyLong_FromUInt32]
added = '3.14'
[function.PyLong_AsInt32]
added = '3.14'
[function.PyLong_AsUInt32]
added = '3.14'
[function.PyLong_FromInt64]
added = '3.14'
[function.PyLong_FromUInt64]
added = '3.14'
[function.PyLong_AsInt64]
added = '3.14'
[function.PyLong_AsUInt64]
added = '3.14'
6 changes: 6 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4046,6 +4046,12 @@ PyInit__testcapi(void)

PyModule_AddIntConstant(m, "the_number_three", 3);
PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT);
PyModule_AddObject(m, "INT32_MIN", PyLong_FromInt32(INT32_MIN));
PyModule_AddObject(m, "INT32_MAX", PyLong_FromInt32(INT32_MAX));
PyModule_AddObject(m, "UINT32_MAX", PyLong_FromUInt32(UINT32_MAX));
PyModule_AddObject(m, "INT64_MIN", PyLong_FromInt64(INT64_MIN));
PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX));
PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX));
Comment on lines +4049 to +4054
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed. You can define them in the Python code as INT32_MAX = 2**31 - 1 etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to reuse <limits.h> C constants to avoid any typo.


if (PyModule_AddIntMacro(m, Py_single_input)) {
return NULL;
Expand Down
54 changes: 52 additions & 2 deletions Modules/_testlimitedcapi/long.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "pyconfig.h" // Py_GIL_DISABLED
#ifndef Py_GIL_DISABLED
// Need limited C API 3.13 to test PyLong_AsInt()
# define Py_LIMITED_API 0x030d0000
// Need limited C API 3.14 to test PyLong_AsInt64()
vstinner marked this conversation as resolved.
Show resolved Hide resolved
# define Py_LIMITED_API 0x030e0000
#endif

#include "parts.h"
Expand Down Expand Up @@ -758,6 +758,52 @@ pylong_aspid(PyObject *module, PyObject *arg)
}


static PyObject *
pylong_asint32(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
int32_t value;
if (PyLong_AsInt32(arg, &value) < 0) {
return NULL;
}
return PyLong_FromInt32(value);
}

static PyObject *
pylong_asuint32(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
uint32_t value;
if (PyLong_AsUInt32(arg, &value) < 0) {
return NULL;
}
return PyLong_FromUInt32(value);
}


static PyObject *
pylong_asint64(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
int64_t value;
if (PyLong_AsInt64(arg, &value) < 0) {
return NULL;
}
return PyLong_FromInt64(value);
}

static PyObject *
pylong_asuint64(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
uint64_t value;
if (PyLong_AsUInt64(arg, &value) < 0) {
return NULL;
}
return PyLong_FromUInt64(value);
}


static PyMethodDef test_methods[] = {
_TESTLIMITEDCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF
_TESTLIMITEDCAPI_TEST_LONG_API_METHODDEF
Expand Down Expand Up @@ -785,6 +831,10 @@ static PyMethodDef test_methods[] = {
{"pylong_asdouble", pylong_asdouble, METH_O},
{"pylong_asvoidptr", pylong_asvoidptr, METH_O},
{"pylong_aspid", pylong_aspid, METH_O},
{"pylong_asint32", pylong_asint32, METH_O},
{"pylong_asuint32", pylong_asuint32, METH_O},
{"pylong_asint64", pylong_asint64, METH_O},
{"pylong_asuint64", pylong_asuint64, METH_O},
{NULL},
};

Expand Down
Loading
Loading