From cb4af47e188576c727d3e5bdb9e7f9ca0d5f8df4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 10 Mar 2024 09:32:50 +0300 Subject: [PATCH 01/14] gh-116560: Add PyLong_Sign() public function * rename _PyLong_Sign() to PyLong_Sign() * add argument checking, documentation and tests * keep _PyLong_Sign() as an alias to PyLong_Sign() --- Doc/c-api/long.rst | 10 ++++++++++ Include/cpython/longobject.h | 10 ++++++---- Lib/test/test_capi/test_long.py | 14 ++++++++++++++ .../2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst | 2 ++ Modules/_ctypes/_ctypes.c | 2 +- Modules/_io/_iomodule.c | 3 +-- Modules/_pickle.c | 2 +- Modules/_testcapi/long.c | 12 ++++++++++++ Modules/_testinternalcapi.c | 4 ++-- Objects/floatobject.c | 2 +- Objects/longobject.c | 13 ++++++++++--- Objects/sliceobject.c | 2 +- 12 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 582f5c7bf05471..88d7e7009e532d 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -442,6 +442,16 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.13 +.. c:function:: int PyLong_Sign(PyObject *obj) + + Return ``-1``, ``0`` or ``+1`` if the integer object *obj* is negative, zero + or positive, respectively. + + Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + + .. versionadded:: 3.13 + + .. c:function:: int PyUnstable_Long_IsCompact(const PyLongObject* op) Return 1 if *op* is compact, 0 otherwise. diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 07251db6bcc203..21ee57733d4c0b 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -41,10 +41,12 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer, PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op); PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); -// _PyLong_Sign. Return 0 if v is 0, -1 if v < 0, +1 if v > 0. -// v must not be NULL, and must be a normalized long. -// There are no error cases. -PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); +/* PyLong_Sign. Return the sign of the integer value: 0, -1 or +1 for + zero, negative and positive integer, respectively. */ +PyAPI_FUNC(int) PyLong_Sign(PyObject *v); + +/* Alias kept for backward compatibility with Python 3.12 */ +#define _PyLong_Sign PyLong_Sign /* _PyLong_FromByteArray: View the n unsigned bytes as a binary integer in base 256, and return a Python int with the same numeric value. diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 9f5ee507a8eb85..98a8bb6d50082a 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -588,6 +588,20 @@ def test_long_fromnativebytes(self): self.assertEqual(expect_u, fromnativebytes(v_be, n, -1, 0), f"PyLong_FromUnsignedNativeBytes(buffer, {n}, )") + def test_long_sign(self): + # Test PyLong_Sign() + check = _testcapi.pylong_sign + self.assertEqual(check(1), 1) + self.assertEqual(check(123456), 1) + self.assertEqual(check(-2), -1) + self.assertEqual(check(0), 0) + self.assertEqual(check(True), 1) + self.assertEqual(check(IntSubclass(-11)), -1) + self.assertEqual(check(False), 0) + + self.assertRaises(TypeError, check, 1.0) + self.assertRaises(SystemError, check, NULL) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst b/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst new file mode 100644 index 00000000000000..157463548b7df2 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyLong_Sign` function (former private :c:func:`!_PyLong_Sign`). +Patch by Sergey B Kirpichev. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 94245ae41afffc..8545e8c3117836 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1440,7 +1440,7 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) goto error; } - if (_PyLong_Sign(length_attr) == -1) { + if (PyLong_Sign(length_attr) == -1) { Py_DECREF(length_attr); PyErr_SetString(PyExc_ValueError, "The '_length_' attribute must not be negative"); diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index 173f5b55e5f732..fc91501964592c 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -10,7 +10,6 @@ #include "Python.h" #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_initconfig.h" // _PyStatus_OK() -#include "pycore_long.h" // _PyLong_Sign() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pystate.h" // _PyInterpreterState_GET() @@ -547,7 +546,7 @@ PyNumber_AsOff_t(PyObject *item, PyObject *err) /* Whether or not it is less than or equal to zero is determined by the sign of ob_size */ - if (_PyLong_Sign(value) < 0) + if (PyLong_Sign(value) < 0) result = PY_OFF_T_MIN; else result = PY_OFF_T_MAX; diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 0d83261168185d..ef18e7e6638376 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -2124,7 +2124,7 @@ save_long(PicklerObject *self, PyObject *obj) unsigned char *pdata; char header[5]; int i; - int sign = _PyLong_Sign(obj); + int sign = PyLong_Sign(obj); if (sign == 0) { header[0] = LONG1; diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index dc21cf9f475228..a7690e5d13544f 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -820,6 +820,17 @@ pylong_fromnativebytes(PyObject *module, PyObject *args) return res; } +static PyObject * +pylong_sign(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + int ret = PyLong_Sign(arg); + if (ret == -1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromLong(ret); +} + static PyMethodDef test_methods[] = { _TESTCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF @@ -851,6 +862,7 @@ static PyMethodDef test_methods[] = { {"pylong_asvoidptr", pylong_asvoidptr, METH_O}, {"pylong_asnativebytes", pylong_asnativebytes, METH_VARARGS}, {"pylong_fromnativebytes", pylong_fromnativebytes, METH_VARARGS}, + {"pylong_sign", pylong_sign, METH_O}, {NULL}, }; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index db8817418950b9..35c90b1f2d9de1 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -22,7 +22,7 @@ #include "pycore_hashtable.h" // _Py_hashtable_new() #include "pycore_initconfig.h" // _Py_GetConfigsAsDict() #include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy() -#include "pycore_long.h" // _PyLong_Sign() +#include "pycore_long.h" // _PyLong_NumBits() #include "pycore_object.h" // _PyObject_IsFreed() #include "pycore_optimizer.h" // _Py_UopsSymbol, etc. #include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal() @@ -1587,7 +1587,7 @@ _testinternalcapi_test_long_numbits_impl(PyObject *module) if (plong == NULL) return NULL; nbits = _PyLong_NumBits(plong); - sign = _PyLong_Sign(plong); + sign = PyLong_Sign(plong); Py_DECREF(plong); if (nbits != testcases[i].nbits) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 37d2d312a6a0b7..6477a3825a61cd 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -426,7 +426,7 @@ float_richcompare(PyObject *v, PyObject *w, int op) else if (PyLong_Check(w)) { int vsign = i == 0.0 ? 0 : i < 0.0 ? -1 : 1; - int wsign = _PyLong_Sign(w); + int wsign = PyLong_Sign(w); size_t nbits; int exponent; diff --git a/Objects/longobject.c b/Objects/longobject.c index 2d1c6ad788e281..70326366df0cae 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -758,12 +758,19 @@ PyLong_AsUnsignedLongMask(PyObject *op) } int -_PyLong_Sign(PyObject *vv) +PyLong_Sign(PyObject *vv) { + if (vv == NULL) { + PyErr_BadInternalCall(); + return -1; + } + if (!PyLong_Check(vv)) { + PyErr_SetString(PyExc_TypeError, "an integer is required"); + return -1; + } + PyLongObject *v = (PyLongObject *)vv; - assert(v != NULL); - assert(PyLong_Check(v)); if (_PyLong_IsCompact(v)) { return _PyLong_CompactSign(v); } diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 7333aea91e5648..e5c6e5d1a4a3c0 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -429,7 +429,7 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, step = evaluate_slice_index(self->step); if (step == NULL) goto error; - step_sign = _PyLong_Sign(step); + step_sign = PyLong_Sign(step); if (step_sign == 0) { PyErr_SetString(PyExc_ValueError, "slice step cannot be zero"); From c38819d2948ae82e8399f4b1d9c224edb821d804 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 10 Mar 2024 16:26:14 +0300 Subject: [PATCH 02/14] + test --- Lib/test/test_capi/test_long.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 98a8bb6d50082a..dd0f79474b42a9 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -600,6 +600,7 @@ def test_long_sign(self): self.assertEqual(check(False), 0) self.assertRaises(TypeError, check, 1.0) + self.assertRaises(TypeError, check, Index(123)) self.assertRaises(SystemError, check, NULL) From 826f537b9625d48f4f4dd0bdedd03bc517a28915 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 11 Mar 2024 04:37:24 +0300 Subject: [PATCH 03/14] Add what's new entry --- Doc/whatsnew/3.13.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 51939909000960..ac4b9774ed020a 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1645,6 +1645,9 @@ New Features between native integer types and Python :class:`int` objects. (Contributed by Steve Dower in :gh:`111140`.) +* Add :c:func:`PyLong_Sign` function (former private :c:func:`!_PyLong_Sign`) + to test sign of :class:`int` objects. Contributed by Sergey B Kirpichev. + Porting to Python 3.13 ---------------------- From 2c0b118a01a06026706b2a331d8a3775daeea06f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 11 Mar 2024 13:43:27 +0300 Subject: [PATCH 04/14] Change PyLong_Sign() API per review request --- Doc/c-api/long.rst | 9 ++++---- Doc/whatsnew/3.13.rst | 4 ++-- Include/cpython/longobject.h | 9 ++++---- ...-03-10-14-55-51.gh-issue-116560.x2mZaO.rst | 3 +-- Modules/_ctypes/_ctypes.c | 2 +- Modules/_io/_iomodule.c | 3 ++- Modules/_pickle.c | 2 +- Modules/_testcapi/long.c | 6 ++--- Modules/_testinternalcapi.c | 4 ++-- Objects/floatobject.c | 2 +- Objects/longobject.c | 23 +++++++++++++------ Objects/sliceobject.c | 2 +- 12 files changed, 39 insertions(+), 30 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 88d7e7009e532d..4af83b5596e96e 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -442,12 +442,13 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.13 -.. c:function:: int PyLong_Sign(PyObject *obj) +.. c:function:: int PyLong_Sign(PyObject *obj, int *sign) - Return ``-1``, ``0`` or ``+1`` if the integer object *obj* is negative, zero - or positive, respectively. + Retrieve the sign of integer object *obj* (``0``, ``-1`` or ``+1`` for zero, + negative or positive integer, respectively) in a variable *sign*. - Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + Return ``0`` on success, else ``-1`` with an exception set. This function + always succeeds if *obj* is a :c:type:`PyLongObject` or its subtype. .. versionadded:: 3.13 diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ac4b9774ed020a..4b9625f76109b0 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1645,8 +1645,8 @@ New Features between native integer types and Python :class:`int` objects. (Contributed by Steve Dower in :gh:`111140`.) -* Add :c:func:`PyLong_Sign` function (former private :c:func:`!_PyLong_Sign`) - to test sign of :class:`int` objects. Contributed by Sergey B Kirpichev. +* Add :c:func:`PyLong_Sign` function to test sign of :class:`int` objects. + Contributed by Sergey B Kirpichev. Porting to Python 3.13 diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 21ee57733d4c0b..2402a80b00ec2d 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -41,12 +41,11 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer, PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op); PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); -/* PyLong_Sign. Return the sign of the integer value: 0, -1 or +1 for - zero, negative and positive integer, respectively. */ -PyAPI_FUNC(int) PyLong_Sign(PyObject *v); +/* PyLong_Sign. Retrieve the sign of the integer value (0, -1 or +1) in a + variable sign. Return 0 on success, else -1 with an exception set. */ +PyAPI_FUNC(int) PyLong_Sign(PyObject *v, int* sign); -/* Alias kept for backward compatibility with Python 3.12 */ -#define _PyLong_Sign PyLong_Sign +PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); /* _PyLong_FromByteArray: View the n unsigned bytes as a binary integer in base 256, and return a Python int with the same numeric value. diff --git a/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst b/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst index 157463548b7df2..88cc9141e65c81 100644 --- a/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst +++ b/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst @@ -1,2 +1 @@ -Add :c:func:`PyLong_Sign` function (former private :c:func:`!_PyLong_Sign`). -Patch by Sergey B Kirpichev. +Add :c:func:`PyLong_Sign` function. Patch by Sergey B Kirpichev. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 8545e8c3117836..94245ae41afffc 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1440,7 +1440,7 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) goto error; } - if (PyLong_Sign(length_attr) == -1) { + if (_PyLong_Sign(length_attr) == -1) { Py_DECREF(length_attr); PyErr_SetString(PyExc_ValueError, "The '_length_' attribute must not be negative"); diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index fc91501964592c..173f5b55e5f732 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -10,6 +10,7 @@ #include "Python.h" #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_initconfig.h" // _PyStatus_OK() +#include "pycore_long.h" // _PyLong_Sign() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pystate.h" // _PyInterpreterState_GET() @@ -546,7 +547,7 @@ PyNumber_AsOff_t(PyObject *item, PyObject *err) /* Whether or not it is less than or equal to zero is determined by the sign of ob_size */ - if (PyLong_Sign(value) < 0) + if (_PyLong_Sign(value) < 0) result = PY_OFF_T_MIN; else result = PY_OFF_T_MAX; diff --git a/Modules/_pickle.c b/Modules/_pickle.c index ef18e7e6638376..0d83261168185d 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -2124,7 +2124,7 @@ save_long(PicklerObject *self, PyObject *obj) unsigned char *pdata; char header[5]; int i; - int sign = PyLong_Sign(obj); + int sign = _PyLong_Sign(obj); if (sign == 0) { header[0] = LONG1; diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index a7690e5d13544f..be00c9080887b0 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -824,11 +824,11 @@ static PyObject * pylong_sign(PyObject *module, PyObject *arg) { NULLABLE(arg); - int ret = PyLong_Sign(arg); - if (ret == -1 && PyErr_Occurred()) { + int sign; + if (PyLong_Sign(arg, &sign) == -1) { return NULL; } - return PyLong_FromLong(ret); + return PyLong_FromLong(sign); } diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 35c90b1f2d9de1..db8817418950b9 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -22,7 +22,7 @@ #include "pycore_hashtable.h" // _Py_hashtable_new() #include "pycore_initconfig.h" // _Py_GetConfigsAsDict() #include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy() -#include "pycore_long.h" // _PyLong_NumBits() +#include "pycore_long.h" // _PyLong_Sign() #include "pycore_object.h" // _PyObject_IsFreed() #include "pycore_optimizer.h" // _Py_UopsSymbol, etc. #include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal() @@ -1587,7 +1587,7 @@ _testinternalcapi_test_long_numbits_impl(PyObject *module) if (plong == NULL) return NULL; nbits = _PyLong_NumBits(plong); - sign = PyLong_Sign(plong); + sign = _PyLong_Sign(plong); Py_DECREF(plong); if (nbits != testcases[i].nbits) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 6477a3825a61cd..37d2d312a6a0b7 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -426,7 +426,7 @@ float_richcompare(PyObject *v, PyObject *w, int op) else if (PyLong_Check(w)) { int vsign = i == 0.0 ? 0 : i < 0.0 ? -1 : 1; - int wsign = PyLong_Sign(w); + int wsign = _PyLong_Sign(w); size_t nbits; int exponent; diff --git a/Objects/longobject.c b/Objects/longobject.c index 70326366df0cae..7046bc8fc6ff69 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -758,7 +758,20 @@ PyLong_AsUnsignedLongMask(PyObject *op) } int -PyLong_Sign(PyObject *vv) +_PyLong_Sign(PyObject *vv) +{ + PyLongObject *v = (PyLongObject *)vv; + + assert(v != NULL); + assert(PyLong_Check(v)); + if (_PyLong_IsCompact(v)) { + return _PyLong_CompactSign(v); + } + return _PyLong_NonCompactSign(v); +} + +int +PyLong_Sign(PyObject *vv, int *sign) { if (vv == NULL) { PyErr_BadInternalCall(); @@ -769,12 +782,8 @@ PyLong_Sign(PyObject *vv) return -1; } - PyLongObject *v = (PyLongObject *)vv; - - if (_PyLong_IsCompact(v)) { - return _PyLong_CompactSign(v); - } - return _PyLong_NonCompactSign(v); + *sign = _PyLong_Sign(vv); + return 0; } static int diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index e5c6e5d1a4a3c0..7333aea91e5648 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -429,7 +429,7 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, step = evaluate_slice_index(self->step); if (step == NULL) goto error; - step_sign = PyLong_Sign(step); + step_sign = _PyLong_Sign(step); if (step_sign == 0) { PyErr_SetString(PyExc_ValueError, "slice step cannot be zero"); From 97776b403147a3ad421150d6117d14f1f97c4af2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 11 Mar 2024 15:27:24 +0300 Subject: [PATCH 05/14] Update Include/cpython/longobject.h Co-authored-by: Victor Stinner --- Include/cpython/longobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 2402a80b00ec2d..6ba79b3d94a649 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -43,7 +43,7 @@ PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); /* PyLong_Sign. Retrieve the sign of the integer value (0, -1 or +1) in a variable sign. Return 0 on success, else -1 with an exception set. */ -PyAPI_FUNC(int) PyLong_Sign(PyObject *v, int* sign); +PyAPI_FUNC(int) PyLong_Sign(PyObject *v, int *sign); PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); From 5f4eb29d54dcb30e338eda881181e59c0f532534 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 11 Mar 2024 15:31:52 +0300 Subject: [PATCH 06/14] Address review; test_long_sign(): check -> sign --- Lib/test/test_capi/test_long.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index dd0f79474b42a9..23e8aab698839c 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -590,18 +590,18 @@ def test_long_fromnativebytes(self): def test_long_sign(self): # Test PyLong_Sign() - check = _testcapi.pylong_sign - self.assertEqual(check(1), 1) - self.assertEqual(check(123456), 1) - self.assertEqual(check(-2), -1) - self.assertEqual(check(0), 0) - self.assertEqual(check(True), 1) - self.assertEqual(check(IntSubclass(-11)), -1) - self.assertEqual(check(False), 0) - - self.assertRaises(TypeError, check, 1.0) - self.assertRaises(TypeError, check, Index(123)) - self.assertRaises(SystemError, check, NULL) + sign = _testcapi.pylong_sign + self.assertEqual(sign(1), 1) + self.assertEqual(sign(123456), 1) + self.assertEqual(sign(-2), -1) + self.assertEqual(sign(0), 0) + self.assertEqual(sign(True), 1) + self.assertEqual(sign(IntSubclass(-11)), -1) + self.assertEqual(sign(False), 0) + + self.assertRaises(TypeError, sign, 1.0) + self.assertRaises(TypeError, sign, Index(123)) + self.assertRaises(SystemError, sign, NULL) if __name__ == "__main__": From 38c6672f1afe32b28d83e9e8fd696ffb9fedd234 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 15 Apr 2024 09:47:14 +0300 Subject: [PATCH 07/14] +1 // damn web editor --- Lib/test/test_capi/test_long.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 6f9cb5e31dcf25..f95ae6084b750d 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -720,7 +720,7 @@ def test_long_fromnativebytes(self): # Py_ASNATIVEBYTES_UNSIGNED_BUFFER flag instead self.assertEqual(expect_u, fromnativebytes(v_be, n, 4, 1), f"PyLong_FromNativeBytes(buffer, {n}, )") - + def test_long_sign(self): # Test PyLong_Sign() sign = _testcapi.pylong_sign @@ -738,4 +738,4 @@ def test_long_sign(self): if __name__ == "__main__": - unittest.mai \ No newline at end of file + unittest.main() From da0ac2b35855acb7d3ea0e58c5bf04ed69a74676 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 2 Jun 2024 19:21:45 +0300 Subject: [PATCH 08/14] Update Objects/longobject.c Co-authored-by: Victor Stinner --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index a5c9ed34c82d82..d11178c5059d5d 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -778,7 +778,7 @@ PyLong_Sign(PyObject *vv, int *sign) return -1; } if (!PyLong_Check(vv)) { - PyErr_SetString(PyExc_TypeError, "an integer is required"); + PyErr_Format(PyExc_TypeError, "expect int, got %T", vv); return -1; } From 41a33b4cdd46957a65a5b70bec61456169d3ba4d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 2 Jun 2024 19:30:32 +0300 Subject: [PATCH 09/14] PyLong_Sign -> PyLong_GetSign --- Doc/c-api/long.rst | 2 +- Doc/whatsnew/3.13.rst | 2 +- Include/cpython/longobject.h | 6 +++--- Lib/test/test_capi/test_long.py | 4 ++-- .../C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst | 2 +- Modules/_testcapi/long.c | 6 +++--- Objects/longobject.c | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 483ad6d5fe6a36..c1ba2bac9abf7e 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -494,7 +494,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.13 -.. c:function:: int PyLong_Sign(PyObject *obj, int *sign) +.. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) Retrieve the sign of integer object *obj* (``0``, ``-1`` or ``+1`` for zero, negative or positive integer, respectively) in a variable *sign*. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index eabaf669e44e15..73fbbd2aec38b2 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2130,7 +2130,7 @@ New Features between native integer types and Python :class:`int` objects. (Contributed by Steve Dower in :gh:`111140`.) -* Add :c:func:`PyLong_Sign` function to test sign of :class:`int` objects. +* Add :c:func:`PyLong_GetSign` function to test sign of :class:`int` objects. Contributed by Sergey B Kirpichev. * Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 5b2e222776f880..7c2e2297d4a740 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -55,9 +55,9 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer, PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op); PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); -/* PyLong_Sign. Retrieve the sign of the integer value (0, -1 or +1) in a - variable sign. Return 0 on success, else -1 with an exception set. */ -PyAPI_FUNC(int) PyLong_Sign(PyObject *v, int *sign); +/* PyLong_GetSign. Retrieve the sign of the integer value (0, -1 or +1) in + a variable sign. Return 0 on success, else -1 with an exception set. */ +PyAPI_FUNC(int) PyLong_GetSign(PyObject *v, int *sign); PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index f95ae6084b750d..bbc45cc087213f 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -722,8 +722,8 @@ def test_long_fromnativebytes(self): f"PyLong_FromNativeBytes(buffer, {n}, )") def test_long_sign(self): - # Test PyLong_Sign() - sign = _testcapi.pylong_sign + # Test PyLong_GetSign() + sign = _testcapi.pylong_getsign self.assertEqual(sign(1), 1) self.assertEqual(sign(123456), 1) self.assertEqual(sign(-2), -1) diff --git a/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst b/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst index 88cc9141e65c81..9bcadfd9247f78 100644 --- a/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst +++ b/Misc/NEWS.d/next/C API/2024-03-10-14-55-51.gh-issue-116560.x2mZaO.rst @@ -1 +1 @@ -Add :c:func:`PyLong_Sign` function. Patch by Sergey B Kirpichev. +Add :c:func:`PyLong_GetSign` function. Patch by Sergey B Kirpichev. diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index f583d186fa5af2..e884a68aa3bf11 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -94,11 +94,11 @@ pylong_fromnativebytes(PyObject *module, PyObject *args) static PyObject * -pylong_sign(PyObject *module, PyObject *arg) +pylong_getsign(PyObject *module, PyObject *arg) { NULLABLE(arg); int sign; - if (PyLong_Sign(arg, &sign) == -1) { + if (PyLong_GetSign(arg, &sign) == -1) { return NULL; } return PyLong_FromLong(sign); @@ -122,7 +122,7 @@ static PyMethodDef test_methods[] = { {"pylong_fromunicodeobject", pylong_fromunicodeobject, METH_VARARGS}, {"pylong_asnativebytes", pylong_asnativebytes, METH_VARARGS}, {"pylong_fromnativebytes", pylong_fromnativebytes, METH_VARARGS}, - {"pylong_sign", pylong_sign, METH_O}, + {"pylong_getsign", pylong_getsign, METH_O}, {"pylong_aspid", pylong_aspid, METH_O}, {NULL}, }; diff --git a/Objects/longobject.c b/Objects/longobject.c index d11178c5059d5d..ee4dcaaa122457 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -771,7 +771,7 @@ _PyLong_Sign(PyObject *vv) } int -PyLong_Sign(PyObject *vv, int *sign) +PyLong_GetSign(PyObject *vv, int *sign) { if (vv == NULL) { PyErr_BadInternalCall(); From 12c6aa0c6a7d274e331a87f63e5df7550fed90c7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 3 Jun 2024 04:33:23 +0300 Subject: [PATCH 10/14] Apply suggestions from code review Co-authored-by: Victor Stinner --- Doc/c-api/long.rst | 6 +++--- Doc/whatsnew/3.13.rst | 4 ++-- Include/cpython/longobject.h | 7 +++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index c1ba2bac9abf7e..5fb92880a8d902 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -496,13 +496,13 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) - Retrieve the sign of integer object *obj* (``0``, ``-1`` or ``+1`` for zero, - negative or positive integer, respectively) in a variable *sign*. + Get the sign of the integer object *obj*: ``0``, ``-1`` or ``+1`` for zero, + negative or positive integer, respectively. Return ``0`` on success, else ``-1`` with an exception set. This function always succeeds if *obj* is a :c:type:`PyLongObject` or its subtype. - .. versionadded:: 3.13 + .. versionadded:: 3.14 .. c:function:: int PyUnstable_Long_IsCompact(const PyLongObject* op) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 73fbbd2aec38b2..8b821b206bb3a0 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2130,8 +2130,8 @@ New Features between native integer types and Python :class:`int` objects. (Contributed by Steve Dower in :gh:`111140`.) -* Add :c:func:`PyLong_GetSign` function to test sign of :class:`int` objects. - Contributed by Sergey B Kirpichev. +* Add :c:func:`PyLong_GetSign` function to get the sign of :class:`int` objects. + (Contributed by Sergey B Kirpichev in :gh:`116560`.) * Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``, diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 7c2e2297d4a740..653270d864a641 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -55,8 +55,11 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer, PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op); PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); -/* PyLong_GetSign. Retrieve the sign of the integer value (0, -1 or +1) in - a variable sign. Return 0 on success, else -1 with an exception set. */ +/* PyLong_GetSign. Get the sign of an integer object: + * 0, -1 or +1 for zero, negative or positive integer, respectively. + * + * - On success, set '*sign' to the integer sign, and return 0. + * - On failure, set an exception, and return -1. */ PyAPI_FUNC(int) PyLong_GetSign(PyObject *v, int *sign); PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); From e02c09597185c61f72ac09bec4a6909d9da2630b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 3 Jun 2024 04:41:39 +0300 Subject: [PATCH 11/14] address review: move news, etc; polishing --- Doc/whatsnew/3.13.rst | 3 --- Doc/whatsnew/3.14.rst | 3 +++ Include/cpython/longobject.h | 10 +++++----- Lib/test/test_capi/test_long.py | 27 ++++++++++++++------------- Modules/_testcapi/long.c | 2 +- Objects/longobject.c | 4 ---- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 8b821b206bb3a0..66626ac06428b9 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2130,9 +2130,6 @@ New Features between native integer types and Python :class:`int` objects. (Contributed by Steve Dower in :gh:`111140`.) -* Add :c:func:`PyLong_GetSign` function to get the sign of :class:`int` objects. - (Contributed by Sergey B Kirpichev in :gh:`116560`.) - * Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``, or ``type.__qualname__`` if ``type.__module__`` is not a string or is equal diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 9f471d24909215..0b71a660cff703 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -248,6 +248,9 @@ C API Changes New Features ------------ +* Add :c:func:`PyLong_GetSign` function to get the sign of :class:`int` objects. + (Contributed by Sergey B Kirpichev in :gh:`116560`.) + Porting to Python 3.14 ---------------------- diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 653270d864a641..19a6722d07734a 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -55,11 +55,11 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer, PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op); PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); -/* PyLong_GetSign. Get the sign of an integer object: - * 0, -1 or +1 for zero, negative or positive integer, respectively. - * - * - On success, set '*sign' to the integer sign, and return 0. - * - On failure, set an exception, and return -1. */ +/* PyLong_GetSign. Get the sign of an integer object: + 0, -1 or +1 for zero, negative or positive integer, respectively. + + - On success, set '*sign' to the integer sign, and return 0. + - On failure, set an exception, and return -1. */ PyAPI_FUNC(int) PyLong_GetSign(PyObject *v, int *sign); PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index bbc45cc087213f..239853cb7673ab 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -721,20 +721,21 @@ def test_long_fromnativebytes(self): self.assertEqual(expect_u, fromnativebytes(v_be, n, 4, 1), f"PyLong_FromNativeBytes(buffer, {n}, )") - def test_long_sign(self): + def test_long_getsign(self): # Test PyLong_GetSign() - sign = _testcapi.pylong_getsign - self.assertEqual(sign(1), 1) - self.assertEqual(sign(123456), 1) - self.assertEqual(sign(-2), -1) - self.assertEqual(sign(0), 0) - self.assertEqual(sign(True), 1) - self.assertEqual(sign(IntSubclass(-11)), -1) - self.assertEqual(sign(False), 0) - - self.assertRaises(TypeError, sign, 1.0) - self.assertRaises(TypeError, sign, Index(123)) - self.assertRaises(SystemError, sign, NULL) + getsign = _testcapi.pylong_getsign + self.assertEqual(getsign(1), 1) + self.assertEqual(getsign(123456), 1) + self.assertEqual(getsign(-2), -1) + self.assertEqual(getsign(0), 0) + self.assertEqual(getsign(True), 1) + self.assertEqual(getsign(IntSubclass(-11)), -1) + self.assertEqual(getsign(False), 0) + + self.assertRaises(TypeError, getsign, 1.0) + self.assertRaises(TypeError, getsign, Index(123)) + + # CRACHES(getsign) if __name__ == "__main__": diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index e884a68aa3bf11..2b5e85d5707522 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -96,8 +96,8 @@ pylong_fromnativebytes(PyObject *module, PyObject *args) static PyObject * pylong_getsign(PyObject *module, PyObject *arg) { - NULLABLE(arg); int sign; + NULLABLE(arg); if (PyLong_GetSign(arg, &sign) == -1) { return NULL; } diff --git a/Objects/longobject.c b/Objects/longobject.c index ee4dcaaa122457..054689471e7aa9 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -773,10 +773,6 @@ _PyLong_Sign(PyObject *vv) int PyLong_GetSign(PyObject *vv, int *sign) { - if (vv == NULL) { - PyErr_BadInternalCall(); - return -1; - } if (!PyLong_Check(vv)) { PyErr_Format(PyExc_TypeError, "expect int, got %T", vv); return -1; From 89a07fee533309affd88d04134690e0e319e00f4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 3 Jun 2024 09:47:50 +0200 Subject: [PATCH 12/14] Update Doc/c-api/long.rst Co-authored-by: Sergey B Kirpichev --- Doc/c-api/long.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 5fb92880a8d902..65ef6eae7bc98d 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -496,11 +496,13 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) - Get the sign of the integer object *obj*: ``0``, ``-1`` or ``+1`` for zero, - negative or positive integer, respectively. + Get the sign of the integer object *obj*. + + On success, set *\*sign* to the integer sign (0, -1 or +1 for zero, negative or + positive integer, respectively) and return 0. - Return ``0`` on success, else ``-1`` with an exception set. This function - always succeeds if *obj* is a :c:type:`PyLongObject` or its subtype. + On failure, return -1 with an exception set. This function always succeeds + if *obj* is a :c:type:`PyLongObject` or its subtype. .. versionadded:: 3.14 From 6b98d70539c84beaa7d98aeced2b272647d977e1 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 3 Jun 2024 10:54:45 +0300 Subject: [PATCH 13/14] +1 --- Doc/c-api/long.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 65ef6eae7bc98d..a0e111af5996d7 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -497,7 +497,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) Get the sign of the integer object *obj*. - + On success, set *\*sign* to the integer sign (0, -1 or +1 for zero, negative or positive integer, respectively) and return 0. From 21eeb54abb30afd5e1904c944d7332e9571765ef Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 3 Jun 2024 12:59:27 +0300 Subject: [PATCH 14/14] Update Lib/test/test_capi/test_long.py --- Lib/test/test_capi/test_long.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 239853cb7673ab..06a29b5a0505b4 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -735,7 +735,7 @@ def test_long_getsign(self): self.assertRaises(TypeError, getsign, 1.0) self.assertRaises(TypeError, getsign, Index(123)) - # CRACHES(getsign) + # CRASHES getsign(NULL) if __name__ == "__main__":