https://github.com/python/cpython/commit/632524a5cbdd3e999992d0e9e68fe8b464ed16ec
commit: 632524a5cbdd3e999992d0e9e68fe8b464ed16ec
branch: main
author: Serhiy Storchaka <storch...@gmail.com>
committer: serhiy-storchaka <storch...@gmail.com>
date: 2025-04-26T17:14:18+03:00
summary:

gh-132987: Support __index__() for "k" and "K" formats in PyArg_Parse 
(GH-132988)

files:
A Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst
M Doc/c-api/arg.rst
M Doc/whatsnew/3.14.rst
M Lib/test/clinic.test.c
M Lib/test/test_capi/test_getargs.py
M Modules/clinic/_cursesmodule.c.h
M Modules/clinic/_testclinic.c.h
M Modules/clinic/fcntlmodule.c.h
M Modules/clinic/posixmodule.c.h
M Modules/clinic/signalmodule.c.h
M Python/getargs.c
M Tools/clinic/libclinic/converters.py

diff --git a/Doc/c-api/arg.rst b/Doc/c-api/arg.rst
index d68dc0885681df..0b05e868917a4e 100644
--- a/Doc/c-api/arg.rst
+++ b/Doc/c-api/arg.rst
@@ -274,6 +274,9 @@ small to receive the value.
    Convert a Python integer to a C :c:expr:`unsigned long` without
    overflow checking.
 
+   .. versionchanged:: next
+      Use :meth:`~object.__index__` if available.
+
 ``L`` (:class:`int`) [long long]
    Convert a Python integer to a C :c:expr:`long long`.
 
@@ -281,6 +284,9 @@ small to receive the value.
    Convert a Python integer to a C :c:expr:`unsigned long long`
    without overflow checking.
 
+   .. versionchanged:: next
+      Use :meth:`~object.__index__` if available.
+
 ``n`` (:class:`int`) [:c:type:`Py_ssize_t`]
    Convert a Python integer to a C :c:type:`Py_ssize_t`.
 
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 4477a82777877d..e9abb41cfd5251 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -2073,6 +2073,11 @@ New features
   Adding ``?`` after any format unit makes ``None`` be accepted as a value.
   (Contributed by Serhiy Storchaka in :gh:`112068`.)
 
+* The ``k`` and ``K`` formats in :c:func:`PyArg_ParseTuple` and
+  similar functions now use :meth:`~object.__index__` if available,
+  like all other integer formats.
+  (Contributed by Serhiy Storchaka in :gh:`112068`.)
+
 * Add macros :c:func:`Py_PACK_VERSION` and :c:func:`Py_PACK_FULL_VERSION` for
   bit-packing Python version numbers.
   (Contributed by Petr Viktorin in :gh:`128629`.)
diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c
index c76da4e2c4f1d5..4a67fcd2c3e9b3 100644
--- a/Lib/test/clinic.test.c
+++ b/Lib/test/clinic.test.c
@@ -1410,7 +1410,7 @@ test_unsigned_long_converter(PyObject *module, PyObject 
*const *args, Py_ssize_t
     if (nargs < 3) {
         goto skip_optional;
     }
-    if (!PyLong_Check(args[2])) {
+    if (!PyIndex_Check(args[2])) {
         _PyArg_BadArgument("test_unsigned_long_converter", "argument 3", 
"int", args[2]);
         goto exit;
     }
@@ -1425,7 +1425,7 @@ test_unsigned_long_converter(PyObject *module, PyObject 
*const *args, Py_ssize_t
 static PyObject *
 test_unsigned_long_converter_impl(PyObject *module, unsigned long a,
                                   unsigned long b, unsigned long c)
-/*[clinic end generated code: output=540bb0ba2894e1fe input=f450d94cae1ef73b]*/
+/*[clinic end generated code: output=d74eed227d77a31b input=f450d94cae1ef73b]*/
 
 
 /*[clinic input]
@@ -1525,7 +1525,7 @@ test_unsigned_long_long_converter(PyObject *module, 
PyObject *const *args, Py_ss
     if (nargs < 3) {
         goto skip_optional;
     }
-    if (!PyLong_Check(args[2])) {
+    if (!PyIndex_Check(args[2])) {
         _PyArg_BadArgument("test_unsigned_long_long_converter", "argument 3", 
"int", args[2]);
         goto exit;
     }
@@ -1542,7 +1542,7 @@ test_unsigned_long_long_converter_impl(PyObject *module,
                                        unsigned long long a,
                                        unsigned long long b,
                                        unsigned long long c)
-/*[clinic end generated code: output=3d69994f618b46bb input=a15115dc41866ff4]*/
+/*[clinic end generated code: output=5ca4e4dfb3db644b input=a15115dc41866ff4]*/
 
 
 /*[clinic input]
diff --git a/Lib/test/test_capi/test_getargs.py 
b/Lib/test/test_capi/test_getargs.py
index b9cad8d2600e56..67a8da7599511f 100644
--- a/Lib/test/test_capi/test_getargs.py
+++ b/Lib/test/test_capi/test_getargs.py
@@ -267,12 +267,12 @@ def test_I(self):
     def test_k(self):
         from _testcapi import getargs_k
         # k returns 'unsigned long', no range checking
-        # it does not accept float, or instances with __int__
         self.assertRaises(TypeError, getargs_k, 3.14)
-        self.assertRaises(TypeError, getargs_k, Index())
+        self.assertEqual(99, getargs_k(Index()))
         self.assertEqual(0, getargs_k(IndexIntSubclass()))
         self.assertRaises(TypeError, getargs_k, BadIndex())
-        self.assertRaises(TypeError, getargs_k, BadIndex2())
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(1, getargs_k(BadIndex2()))
         self.assertEqual(0, getargs_k(BadIndex3()))
         self.assertRaises(TypeError, getargs_k, Int())
         self.assertEqual(0, getargs_k(IntSubclass()))
@@ -419,10 +419,11 @@ def test_K(self):
         from _testcapi import getargs_K
         # K return 'unsigned long long', no range checking
         self.assertRaises(TypeError, getargs_K, 3.14)
-        self.assertRaises(TypeError, getargs_K, Index())
+        self.assertEqual(99, getargs_K(Index()))
         self.assertEqual(0, getargs_K(IndexIntSubclass()))
         self.assertRaises(TypeError, getargs_K, BadIndex())
-        self.assertRaises(TypeError, getargs_K, BadIndex2())
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(1, getargs_K(BadIndex2()))
         self.assertEqual(0, getargs_K(BadIndex3()))
         self.assertRaises(TypeError, getargs_K, Int())
         self.assertEqual(0, getargs_K(IntSubclass()))
@@ -432,6 +433,7 @@ def test_K(self):
 
         self.assertEqual(ULLONG_MAX, getargs_K(ULLONG_MAX))
         self.assertEqual(0, getargs_K(0))
+        self.assertEqual(ULLONG_MAX, getargs_K(ULLONG_MAX))
         self.assertEqual(0, getargs_K(ULLONG_MAX+1))
 
         self.assertEqual(42, getargs_K(42))
diff --git 
a/Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst 
b/Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst
new file mode 100644
index 00000000000000..e4a33e61319daf
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst
@@ -0,0 +1,2 @@
+The ``k`` and ``K`` formats in :c:func:`PyArg_Parse` now support the
+:meth:`~object.__index__` special method, like all other integer formats.
diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h
index 0346cc88782941..3a1c1698b1b8c6 100644
--- a/Modules/clinic/_cursesmodule.c.h
+++ b/Modules/clinic/_cursesmodule.c.h
@@ -2388,7 +2388,7 @@ _curses_ungetmouse(PyObject *module, PyObject *const 
*args, Py_ssize_t nargs)
     if (z == -1 && PyErr_Occurred()) {
         goto exit;
     }
-    if (!PyLong_Check(args[4])) {
+    if (!PyIndex_Check(args[4])) {
         _PyArg_BadArgument("ungetmouse", "argument 5", "int", args[4]);
         goto exit;
     }
@@ -3154,7 +3154,7 @@ _curses_mousemask(PyObject *module, PyObject *arg)
     PyObject *return_value = NULL;
     unsigned long newmask;
 
-    if (!PyLong_Check(arg)) {
+    if (!PyIndex_Check(arg)) {
         _PyArg_BadArgument("mousemask", "argument", "int", arg);
         goto exit;
     }
@@ -4394,4 +4394,4 @@ _curses_has_extended_color_support(PyObject *module, 
PyObject *Py_UNUSED(ignored
 #ifndef _CURSES_USE_DEFAULT_COLORS_METHODDEF
     #define _CURSES_USE_DEFAULT_COLORS_METHODDEF
 #endif /* !defined(_CURSES_USE_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=acae2eb9cf75e76d input=a9049054013a1b77]*/
+/*[clinic end generated code: output=dbbbe86a4171799a input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h
index 636eea665e9025..970528ce9ea46d 100644
--- a/Modules/clinic/_testclinic.c.h
+++ b/Modules/clinic/_testclinic.c.h
@@ -1038,7 +1038,7 @@ unsigned_long_converter(PyObject *module, PyObject *const 
*args, Py_ssize_t narg
     if (nargs < 3) {
         goto skip_optional;
     }
-    if (!PyLong_Check(args[2])) {
+    if (!PyIndex_Check(args[2])) {
         _PyArg_BadArgument("unsigned_long_converter", "argument 3", "int", 
args[2]);
         goto exit;
     }
@@ -1122,7 +1122,7 @@ unsigned_long_long_converter(PyObject *module, PyObject 
*const *args, Py_ssize_t
     if (nargs < 3) {
         goto skip_optional;
     }
-    if (!PyLong_Check(args[2])) {
+    if (!PyIndex_Check(args[2])) {
         _PyArg_BadArgument("unsigned_long_long_converter", "argument 3", 
"int", args[2]);
         goto exit;
     }
@@ -4481,4 +4481,4 @@ 
_testclinic_TestClass_posonly_poskw_varpos_array_no_fastcall(PyObject *type, PyO
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=b57b94aeba0882b4 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=84ffc31f27215baa input=a9049054013a1b77]*/
diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h
index d8721e9ea99dd9..00a929064ba700 100644
--- a/Modules/clinic/fcntlmodule.c.h
+++ b/Modules/clinic/fcntlmodule.c.h
@@ -120,7 +120,7 @@ fcntl_ioctl(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs)
     if (fd < 0) {
         goto exit;
     }
-    if (!PyLong_Check(args[1])) {
+    if (!PyIndex_Check(args[1])) {
         PyErr_Format(PyExc_TypeError, "ioctl() argument 2 must be int, not 
%T", args[1]);
         goto exit;
     }
@@ -264,4 +264,4 @@ fcntl_lockf(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=30c01b46bfa840c1 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=65a16bc64c7b4de4 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index d03f68ab8fb9aa..0125e247ee44d3 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -840,7 +840,7 @@ os_chflags(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs, PyObject *
     if (!path_converter(args[0], &path)) {
         goto exit;
     }
-    if (!PyLong_Check(args[1])) {
+    if (!PyIndex_Check(args[1])) {
         _PyArg_BadArgument("chflags", "argument 'flags'", "int", args[1]);
         goto exit;
     }
@@ -924,7 +924,7 @@ os_lchflags(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs, PyObject
     if (!path_converter(args[0], &path)) {
         goto exit;
     }
-    if (!PyLong_Check(args[1])) {
+    if (!PyIndex_Check(args[1])) {
         _PyArg_BadArgument("lchflags", "argument 'flags'", "int", args[1]);
         goto exit;
     }
@@ -13398,4 +13398,4 @@ os__emscripten_debugger(PyObject *module, PyObject 
*Py_UNUSED(ignored))
 #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF
     #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF
 #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */
-/*[clinic end generated code: output=35dd8edb53b50537 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=a5ca2541f2af5462 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h
index 3ae8be84ccbf9c..955861b4da37d9 100644
--- a/Modules/clinic/signalmodule.c.h
+++ b/Modules/clinic/signalmodule.c.h
@@ -656,7 +656,7 @@ signal_pthread_kill(PyObject *module, PyObject *const 
*args, Py_ssize_t nargs)
     if (!_PyArg_CheckPositional("pthread_kill", nargs, 2, 2)) {
         goto exit;
     }
-    if (!PyLong_Check(args[0])) {
+    if (!PyIndex_Check(args[0])) {
         _PyArg_BadArgument("pthread_kill", "argument 1", "int", args[0]);
         goto exit;
     }
@@ -779,4 +779,4 @@ signal_pidfd_send_signal(PyObject *module, PyObject *const 
*args, Py_ssize_t nar
 #ifndef SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF
     #define SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF
 #endif /* !defined(SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF) */
-/*[clinic end generated code: output=a8b1ac2fc44a007e input=a9049054013a1b77]*/
+/*[clinic end generated code: output=48bfaffeb25df5d2 input=a9049054013a1b77]*/
diff --git a/Python/getargs.c b/Python/getargs.c
index 16d5e52742d129..0cf596285cc7b5 100644
--- a/Python/getargs.c
+++ b/Python/getargs.c
@@ -839,10 +839,13 @@ convertsimple(PyObject *arg, const char **p_format, 
va_list *p_va, int flags,
         unsigned long *p = va_arg(*p_va, unsigned long *);
         HANDLE_NULLABLE;
         unsigned long ival;
-        if (PyLong_Check(arg))
-            ival = PyLong_AsUnsignedLongMask(arg);
-        else
+        if (!PyIndex_Check(arg)) {
             return converterr(nullable, "int", arg, msgbuf, bufsize);
+        }
+        ival = PyLong_AsUnsignedLongMask(arg);
+        if (ival == (unsigned long)(long)-1 && PyErr_Occurred()) {
+            RETURN_ERR_OCCURRED;
+        }
         *p = ival;
         break;
     }
@@ -862,10 +865,13 @@ convertsimple(PyObject *arg, const char **p_format, 
va_list *p_va, int flags,
         unsigned long long *p = va_arg(*p_va, unsigned long long *);
         HANDLE_NULLABLE;
         unsigned long long ival;
-        if (PyLong_Check(arg))
-            ival = PyLong_AsUnsignedLongLongMask(arg);
-        else
+        if (!PyIndex_Check(arg)) {
             return converterr(nullable, "int", arg, msgbuf, bufsize);
+        }
+        ival = PyLong_AsUnsignedLongLongMask(arg);
+        if (ival == (unsigned long long)(long long)-1 && PyErr_Occurred()) {
+            RETURN_ERR_OCCURRED;
+        }
         *p = ival;
         break;
     }
diff --git a/Tools/clinic/libclinic/converters.py 
b/Tools/clinic/libclinic/converters.py
index b176ee4ecd444a..b0557ae54c1362 100644
--- a/Tools/clinic/libclinic/converters.py
+++ b/Tools/clinic/libclinic/converters.py
@@ -386,7 +386,7 @@ def use_converter(self) -> None:
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'k':
             return self.format_code("""
-                if (!PyLong_Check({argname})) {{{{
+                if (!PyIndex_Check({argname})) {{{{
                     {bad_argument}
                     goto exit;
                 }}}}
@@ -444,7 +444,7 @@ def use_converter(self) -> None:
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'K':
             return self.format_code("""
-                if (!PyLong_Check({argname})) {{{{
+                if (!PyIndex_Check({argname})) {{{{
                     {bad_argument}
                     goto exit;
                 }}}}

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to