Skip to content

Commit 1a2a457

Browse files
committed
Add N format unit for non-negative Py_ssize_t
1 parent 7c92497 commit 1a2a457

File tree

5 files changed

+64
-0
lines changed

5 files changed

+64
-0
lines changed

Doc/c-api/arg.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ the minimal value for the corresponding signed integer type of the same size.
285285
``n`` (:class:`int`) [:c:type:`Py_ssize_t`]
286286
Convert a Python integer to a C :c:type:`Py_ssize_t`.
287287

288+
``N`` (:class:`int`) [:c:type:`Py_ssize_t`]
289+
Convert a non-negative Python integer to a C :c:type:`Py_ssize_t`.
290+
288291
``c`` (:class:`bytes` or :class:`bytearray` of length 1) [char]
289292
Convert a Python byte, represented as a :class:`bytes` or
290293
:class:`bytearray` object of length 1, to a C :c:expr:`char`.

Lib/test/test_capi/test_getargs.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,32 @@ def test_I(self):
290290
with self.assertWarns(DeprecationWarning):
291291
self.assertEqual(UINT_MAX & -VERY_LARGE, getargs_I(-VERY_LARGE))
292292

293+
def test_N(self):
294+
from _testcapi import getargs_N
295+
# n returns 'Py_ssize_t', and does range checking
296+
# (0 ... PY_SSIZE_T_MAX)
297+
self.assertRaises(TypeError, getargs_N, 3.14)
298+
self.assertEqual(99, getargs_N(Index()))
299+
self.assertEqual(0, getargs_N(IndexIntSubclass()))
300+
self.assertRaises(TypeError, getargs_N, BadIndex())
301+
with self.assertWarns(DeprecationWarning):
302+
self.assertEqual(1, getargs_N(BadIndex2()))
303+
self.assertEqual(0, getargs_N(BadIndex3()))
304+
self.assertRaises(TypeError, getargs_N, Int())
305+
self.assertEqual(0, getargs_N(IntSubclass()))
306+
self.assertRaises(TypeError, getargs_N, BadInt())
307+
self.assertRaises(TypeError, getargs_N, BadInt2())
308+
self.assertEqual(0, getargs_N(BadInt3()))
309+
310+
self.assertRaises(OverflowError, getargs_N, PY_SSIZE_T_MIN-1)
311+
self.assertRaises(OverflowError, getargs_N, PY_SSIZE_T_MIN)
312+
self.assertRaises(OverflowError, getargs_N, -1)
313+
self.assertEqual(PY_SSIZE_T_MAX, getargs_N(PY_SSIZE_T_MAX))
314+
self.assertRaises(OverflowError, getargs_N, PY_SSIZE_T_MAX+1)
315+
316+
self.assertEqual(42, getargs_N(42))
317+
self.assertRaises(OverflowError, getargs_N, VERY_LARGE)
318+
293319
def test_k(self):
294320
from _testcapi import getargs_k
295321
# k returns 'unsigned long', no range checking
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added ``N`` as new ``PyArg_Parse`` format unit for non-negative ``Py_ssize_t``
2+
values.

Modules/_testcapi/getargs.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,16 @@ getargs_n(PyObject *self, PyObject *args)
437437
return PyLong_FromSsize_t(value);
438438
}
439439

440+
static PyObject *
441+
getargs_N(PyObject *self, PyObject *args)
442+
{
443+
Py_ssize_t value;
444+
if (!PyArg_ParseTuple(args, "N", &value)) {
445+
return NULL;
446+
}
447+
return PyLong_FromSsize_t(value);
448+
}
449+
440450
static PyObject *
441451
getargs_p(PyObject *self, PyObject *args)
442452
{
@@ -793,6 +803,7 @@ static PyMethodDef test_methods[] = {
793803
{"getargs_keywords", _PyCFunction_CAST(getargs_keywords), METH_VARARGS|METH_KEYWORDS},
794804
{"getargs_l", getargs_l, METH_VARARGS},
795805
{"getargs_n", getargs_n, METH_VARARGS},
806+
{"getargs_N", getargs_N, METH_VARARGS},
796807
{"getargs_p", getargs_p, METH_VARARGS},
797808
{"getargs_positional_only_and_keywords", _PyCFunction_CAST(getargs_positional_only_and_keywords), METH_VARARGS|METH_KEYWORDS},
798809
{"getargs_s", getargs_s, METH_VARARGS},

Python/getargs.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,27 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
814814
*p = ival;
815815
break;
816816
}
817+
818+
case 'N': { /* Py_ssize_t - non negative */
819+
PyObject *iobj;
820+
Py_ssize_t *p = va_arg(*p_va, Py_ssize_t *);
821+
Py_ssize_t ival = -1;
822+
iobj = _PyNumber_Index(arg);
823+
if (iobj != NULL) {
824+
ival = PyLong_AsSsize_t(iobj);
825+
Py_DECREF(iobj);
826+
}
827+
if (ival == -1 && PyErr_Occurred())
828+
RETURN_ERR_OCCURRED;
829+
else if (ival < 0) {
830+
PyErr_SetString(PyExc_OverflowError,
831+
"integer is less than minimum");
832+
RETURN_ERR_OCCURRED;
833+
}
834+
*p = ival;
835+
break;
836+
}
837+
817838
case 'l': {/* long int */
818839
long *p = va_arg(*p_va, long *);
819840
long ival = PyLong_AsLong(arg);
@@ -2618,6 +2639,7 @@ skipitem(const char **p_format, va_list *p_va, int flags)
26182639
case 'L': /* long long */
26192640
case 'K': /* long long sized bitfield */
26202641
case 'n': /* Py_ssize_t */
2642+
case 'N': /* Py_ssize_t - non negative */
26212643
case 'f': /* float */
26222644
case 'd': /* double */
26232645
case 'D': /* complex double */

0 commit comments

Comments
 (0)