Skip to content

Commit a4ebeec

Browse files
Revert "gh-112068: C API: Add support of nullable arguments in PyArg_Parse (GH-121303)"
This reverts commit f5f1ac8.
1 parent 4d02f31 commit a4ebeec

File tree

10 files changed

+137
-316
lines changed

10 files changed

+137
-316
lines changed

Doc/c-api/arg.rst

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,14 @@ There are three ways strings and buffers can be converted to C:
113113
``z`` (:class:`str` or ``None``) [const char \*]
114114
Like ``s``, but the Python object may also be ``None``, in which case the C
115115
pointer is set to ``NULL``.
116-
It is the same as ``s?`` with the C pointer was initialized to ``NULL``.
117116

118117
``z*`` (:class:`str`, :term:`bytes-like object` or ``None``) [Py_buffer]
119118
Like ``s*``, but the Python object may also be ``None``, in which case the
120119
``buf`` member of the :c:type:`Py_buffer` structure is set to ``NULL``.
121-
It is the same as ``s*?`` with the ``buf`` member of the :c:type:`Py_buffer`
122-
structure was initialized to ``NULL``.
123120

124121
``z#`` (:class:`str`, read-only :term:`bytes-like object` or ``None``) [const char \*, :c:type:`Py_ssize_t`]
125122
Like ``s#``, but the Python object may also be ``None``, in which case the C
126123
pointer is set to ``NULL``.
127-
It is the same as ``s#?`` with the C pointer was initialized to ``NULL``.
128124

129125
``y`` (read-only :term:`bytes-like object`) [const char \*]
130126
This format converts a bytes-like object to a C pointer to a
@@ -394,17 +390,6 @@ Other objects
394390
Non-tuple sequences are deprecated if *items* contains format units
395391
which store a borrowed buffer or a borrowed reference.
396392

397-
``unit?`` (anything or ``None``) [*matching-variable(s)*]
398-
``?`` modifies the behavior of the preceding format unit.
399-
The C variable(s) corresponding to that parameter should be initialized
400-
to their default value --- when the argument is ``None``,
401-
:c:func:`PyArg_ParseTuple` does not touch the contents of the corresponding
402-
C variable(s).
403-
If the argument is not ``None``, it is parsed according to the specified
404-
format unit.
405-
406-
.. versionadded:: 3.14
407-
408393
A few other characters have a meaning in a format string. These may not occur
409394
inside nested parentheses. They are:
410395

Doc/whatsnew/3.14.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,11 +2940,6 @@ New features
29402940
file.
29412941
(Contributed by Victor Stinner in :gh:`127350`.)
29422942

2943-
* Add support of nullable arguments in :c:func:`PyArg_ParseTuple` and
2944-
similar functions.
2945-
Adding ``?`` after any format unit makes ``None`` be accepted as a value.
2946-
(Contributed by Serhiy Storchaka in :gh:`112068`.)
2947-
29482943
* The ``k`` and ``K`` formats in :c:func:`PyArg_ParseTuple` and
29492944
similar functions now use :meth:`~object.__index__` if available,
29502945
like all other integer formats.

Lib/test/test_capi/test_getargs.py

Lines changed: 0 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,123 +1429,6 @@ def test_nested_sequence(self):
14291429
"argument 1 must be sequence of length 1, not 0"):
14301430
parse(([],), {}, '(' + f + ')', ['a'])
14311431

1432-
def test_specific_type_errors(self):
1433-
parse = _testcapi.parse_tuple_and_keywords
1434-
1435-
def check(format, arg, expected, got='list'):
1436-
errmsg = f'must be {expected}, not {got}'
1437-
with self.assertRaisesRegex(TypeError, errmsg):
1438-
parse((arg,), {}, format, ['a'])
1439-
1440-
check('k', [], 'int')
1441-
check('k?', [], 'int or None')
1442-
check('K', [], 'int')
1443-
check('K?', [], 'int or None')
1444-
check('c', [], 'a byte string of length 1')
1445-
check('c?', [], 'a byte string of length 1 or None')
1446-
check('c', b'abc', 'a byte string of length 1',
1447-
'a bytes object of length 3')
1448-
check('c?', b'abc', 'a byte string of length 1 or None',
1449-
'a bytes object of length 3')
1450-
check('c', bytearray(b'abc'), 'a byte string of length 1',
1451-
'a bytearray object of length 3')
1452-
check('c?', bytearray(b'abc'), 'a byte string of length 1 or None',
1453-
'a bytearray object of length 3')
1454-
check('C', [], 'a unicode character')
1455-
check('C?', [], 'a unicode character or None')
1456-
check('C', 'abc', 'a unicode character',
1457-
'a string of length 3')
1458-
check('C?', 'abc', 'a unicode character or None',
1459-
'a string of length 3')
1460-
check('s', [], 'str')
1461-
check('s?', [], 'str or None')
1462-
check('z', [], 'str or None')
1463-
check('z?', [], 'str or None')
1464-
check('es', [], 'str')
1465-
check('es?', [], 'str or None')
1466-
check('es#', [], 'str')
1467-
check('es#?', [], 'str or None')
1468-
check('et', [], 'str, bytes or bytearray')
1469-
check('et?', [], 'str, bytes, bytearray or None')
1470-
check('et#', [], 'str, bytes or bytearray')
1471-
check('et#?', [], 'str, bytes, bytearray or None')
1472-
check('w*', [], 'read-write bytes-like object')
1473-
check('w*?', [], 'read-write bytes-like object or None')
1474-
check('S', [], 'bytes')
1475-
check('S?', [], 'bytes or None')
1476-
check('U', [], 'str')
1477-
check('U?', [], 'str or None')
1478-
check('Y', [], 'bytearray')
1479-
check('Y?', [], 'bytearray or None')
1480-
check('(OO)', 42, '2-item tuple', 'int')
1481-
check('(OO)?', 42, '2-item tuple or None', 'int')
1482-
check('(OO)', (1, 2, 3), 'tuple of length 2', '3')
1483-
1484-
def test_nullable(self):
1485-
parse = _testcapi.parse_tuple_and_keywords
1486-
1487-
def check(format, arg, allows_none=False):
1488-
# Because some format units (such as y*) require cleanup,
1489-
# we force the parsing code to perform the cleanup by adding
1490-
# an argument that always fails.
1491-
# By checking for an exception, we ensure that the parsing
1492-
# of the first argument was successful.
1493-
self.assertRaises(OverflowError, parse,
1494-
(arg, 256), {}, format + '?b', ['a', 'b'])
1495-
self.assertRaises(OverflowError, parse,
1496-
(None, 256), {}, format + '?b', ['a', 'b'])
1497-
self.assertRaises(OverflowError, parse,
1498-
(arg, 256), {}, format + 'b', ['a', 'b'])
1499-
self.assertRaises(OverflowError if allows_none else TypeError, parse,
1500-
(None, 256), {}, format + 'b', ['a', 'b'])
1501-
1502-
check('b', 42)
1503-
check('B', 42)
1504-
check('h', 42)
1505-
check('H', 42)
1506-
check('i', 42)
1507-
check('I', 42)
1508-
check('n', 42)
1509-
check('l', 42)
1510-
check('k', 42)
1511-
check('L', 42)
1512-
check('K', 42)
1513-
check('f', 2.5)
1514-
check('d', 2.5)
1515-
check('D', 2.5j)
1516-
check('c', b'a')
1517-
check('C', 'a')
1518-
check('p', True, allows_none=True)
1519-
check('y', b'buffer')
1520-
check('y*', b'buffer')
1521-
check('y#', b'buffer')
1522-
check('s', 'string')
1523-
check('s*', 'string')
1524-
check('s#', 'string')
1525-
check('z', 'string', allows_none=True)
1526-
check('z*', 'string', allows_none=True)
1527-
check('z#', 'string', allows_none=True)
1528-
check('w*', bytearray(b'buffer'))
1529-
check('U', 'string')
1530-
check('S', b'bytes')
1531-
check('Y', bytearray(b'bytearray'))
1532-
check('O', object, allows_none=True)
1533-
1534-
check('(OO)', (1, 2))
1535-
self.assertEqual(parse((((1, 2), 3),), {}, '((OO)?O)', ['a']), (1, 2, 3))
1536-
self.assertEqual(parse(((None, 3),), {}, '((OO)?O)', ['a']), (NULL, NULL, 3))
1537-
self.assertEqual(parse((((1, 2), 3),), {}, '((OO)O)', ['a']), (1, 2, 3))
1538-
self.assertRaises(TypeError, parse, ((None, 3),), {}, '((OO)O)', ['a'])
1539-
1540-
parse((None,), {}, 'es?', ['a'])
1541-
parse((None,), {}, 'es#?', ['a'])
1542-
parse((None,), {}, 'et?', ['a'])
1543-
parse((None,), {}, 'et#?', ['a'])
1544-
parse((None,), {}, 'O!?', ['a'])
1545-
parse((None,), {}, 'O&?', ['a'])
1546-
1547-
# TODO: More tests for es?, es#?, et?, et#?, O!, O&
1548-
15491432
@unittest.skipIf(_testinternalcapi is None, 'needs _testinternalcapi')
15501433
def test_gh_119213(self):
15511434
rc, out, err = script_helper.assert_python_ok("-c", """if True:

Lib/test/test_mmap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ def test_tagname(self):
732732
m2.close()
733733
m1.close()
734734

735-
with self.assertRaisesRegex(TypeError, 'must be str or None'):
735+
with self.assertRaisesRegex(TypeError, 'tagname'):
736736
mmap.mmap(-1, 8, tagname=1)
737737

738738
@cpython_only

Modules/_ctypes/_ctypes.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3937,7 +3937,9 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags)
39373937
PyObject *name = Py_None;
39383938
PyObject *defval;
39393939
PyObject *typ;
3940-
if (!PyArg_ParseTuple(item, "i|U?O", &flag, &name, &defval)) {
3940+
if (!PyArg_ParseTuple(item, "i|OO", &flag, &name, &defval) ||
3941+
!(name == Py_None || PyUnicode_Check(name)))
3942+
{
39413943
PyErr_SetString(PyExc_TypeError,
39423944
"paramflags must be a sequence of (int [,string [,value]]) tuples");
39433945
return 0;
@@ -4002,8 +4004,10 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args, PyObject *kwds)
40024004
void *handle;
40034005
PyObject *paramflags = NULL;
40044006

4005-
if (!PyArg_ParseTuple(args, "O|O?", &ftuple, &paramflags))
4007+
if (!PyArg_ParseTuple(args, "O|O", &ftuple, &paramflags))
40064008
return NULL;
4009+
if (paramflags == Py_None)
4010+
paramflags = NULL;
40074011

40084012
ftuple = PySequence_Tuple(ftuple);
40094013
if (!ftuple)
@@ -4135,8 +4139,10 @@ PyCFuncPtr_FromVtblIndex(PyTypeObject *type, PyObject *args, PyObject *kwds)
41354139
GUID *iid = NULL;
41364140
Py_ssize_t iid_len = 0;
41374141

4138-
if (!PyArg_ParseTuple(args, "is|O?z#", &index, &name, &paramflags, &iid, &iid_len))
4142+
if (!PyArg_ParseTuple(args, "is|Oz#", &index, &name, &paramflags, &iid, &iid_len))
41394143
return NULL;
4144+
if (paramflags == Py_None)
4145+
paramflags = NULL;
41404146

41414147
ctypes_state *st = get_module_state_by_def(Py_TYPE(type));
41424148
if (!_validate_paramflags(st, type, paramflags)) {

Modules/_interpretersmodule.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,11 +1415,14 @@ interp_get_config(PyObject *self, PyObject *args, PyObject *kwds)
14151415
PyObject *idobj = NULL;
14161416
int restricted = 0;
14171417
if (!PyArg_ParseTupleAndKeywords(args, kwds,
1418-
"O?|$p:get_config", kwlist,
1418+
"O|$p:get_config", kwlist,
14191419
&idobj, &restricted))
14201420
{
14211421
return NULL;
14221422
}
1423+
if (idobj == Py_None) {
1424+
idobj = NULL;
1425+
}
14231426

14241427
int reqready = 0;
14251428
PyInterpreterState *interp = \
@@ -1536,14 +1539,14 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
15361539
static char *kwlist[] = {"exc", NULL};
15371540
PyObject *exc_arg = NULL;
15381541
if (!PyArg_ParseTupleAndKeywords(args, kwds,
1539-
"|O?:capture_exception", kwlist,
1542+
"|O:capture_exception", kwlist,
15401543
&exc_arg))
15411544
{
15421545
return NULL;
15431546
}
15441547

15451548
PyObject *exc = exc_arg;
1546-
if (exc == NULL) {
1549+
if (exc == NULL || exc == Py_None) {
15471550
exc = PyErr_GetRaisedException();
15481551
if (exc == NULL) {
15491552
Py_RETURN_NONE;

Modules/_json.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,16 +1222,23 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
12221222
static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL};
12231223

12241224
PyEncoderObject *s;
1225-
PyObject *markers = Py_None, *defaultfn, *encoder, *indent, *key_separator;
1225+
PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
12261226
PyObject *item_separator;
12271227
int sort_keys, skipkeys, allow_nan;
12281228

1229-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!?OOOUUppp:make_encoder", kwlist,
1230-
&PyDict_Type, &markers, &defaultfn, &encoder, &indent,
1229+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOUUppp:make_encoder", kwlist,
1230+
&markers, &defaultfn, &encoder, &indent,
12311231
&key_separator, &item_separator,
12321232
&sort_keys, &skipkeys, &allow_nan))
12331233
return NULL;
12341234

1235+
if (markers != Py_None && !PyDict_Check(markers)) {
1236+
PyErr_Format(PyExc_TypeError,
1237+
"make_encoder() argument 1 must be dict or None, "
1238+
"not %.200s", Py_TYPE(markers)->tp_name);
1239+
return NULL;
1240+
}
1241+
12351242
s = (PyEncoderObject *)type->tp_alloc(type, 0);
12361243
if (s == NULL)
12371244
return NULL;

Modules/_threadmodule.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -681,12 +681,12 @@ PyThreadHandleObject_join(PyObject *op, PyObject *args)
681681
PyThreadHandleObject *self = PyThreadHandleObject_CAST(op);
682682

683683
PyObject *timeout_obj = NULL;
684-
if (!PyArg_ParseTuple(args, "|O?:join", &timeout_obj)) {
684+
if (!PyArg_ParseTuple(args, "|O:join", &timeout_obj)) {
685685
return NULL;
686686
}
687687

688688
PyTime_t timeout_ns = -1;
689-
if (timeout_obj != NULL) {
689+
if (timeout_obj != NULL && timeout_obj != Py_None) {
690690
if (_PyTime_FromSecondsObject(&timeout_ns, timeout_obj,
691691
_PyTime_ROUND_TIMEOUT) < 0) {
692692
return NULL;
@@ -1957,10 +1957,10 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs,
19571957
PyObject *func = NULL;
19581958
int daemon = 1;
19591959
thread_module_state *state = get_thread_state(module);
1960-
PyObject *hobj = Py_None;
1960+
PyObject *hobj = NULL;
19611961
if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs,
1962-
"O|O!?p:start_joinable_thread", keywords,
1963-
&func, state->thread_handle_type, &hobj, &daemon)) {
1962+
"O|Op:start_joinable_thread", keywords,
1963+
&func, &hobj, &daemon)) {
19641964
return NULL;
19651965
}
19661966

@@ -1970,6 +1970,14 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs,
19701970
return NULL;
19711971
}
19721972

1973+
if (hobj == NULL) {
1974+
hobj = Py_None;
1975+
}
1976+
else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) {
1977+
PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle");
1978+
return NULL;
1979+
}
1980+
19731981
if (PySys_Audit("_thread.start_joinable_thread", "OiO", func, daemon,
19741982
hobj) < 0) {
19751983
return NULL;

Modules/mmapmodule.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#endif
2424

2525
#include <Python.h>
26+
#include "pycore_abstract.h" // _Py_convert_optional_to_ssize_t()
2627
#include "pycore_bytesobject.h" // _PyBytes_Find()
2728
#include "pycore_fileutils.h" // _Py_stat_struct
2829
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
@@ -529,7 +530,7 @@ mmap_read_method(PyObject *op, PyObject *args)
529530
mmap_object *self = mmap_object_CAST(op);
530531

531532
CHECK_VALID(NULL);
532-
if (!PyArg_ParseTuple(args, "|n?:read", &num_bytes))
533+
if (!PyArg_ParseTuple(args, "|O&:read", _Py_convert_optional_to_ssize_t, &num_bytes))
533534
return NULL;
534535
CHECK_VALID(NULL);
535536

@@ -1723,7 +1724,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
17231724
DWORD off_lo; /* lower 32 bits of offset */
17241725
DWORD size_hi; /* upper 32 bits of size */
17251726
DWORD size_lo; /* lower 32 bits of size */
1726-
PyObject *tagname = NULL;
1727+
PyObject *tagname = Py_None;
17271728
DWORD dwErr = 0;
17281729
int fileno;
17291730
HANDLE fh = 0;
@@ -1733,7 +1734,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
17331734
"tagname",
17341735
"access", "offset", NULL };
17351736

1736-
if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|U?iL", keywords,
1737+
if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|OiL", keywords,
17371738
&fileno, &map_size,
17381739
&tagname, &access, &offset)) {
17391740
return NULL;
@@ -1866,7 +1867,13 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
18661867
m_obj->weakreflist = NULL;
18671868
m_obj->exports = 0;
18681869
/* set the tag name */
1869-
if (tagname != NULL) {
1870+
if (!Py_IsNone(tagname)) {
1871+
if (!PyUnicode_Check(tagname)) {
1872+
Py_DECREF(m_obj);
1873+
return PyErr_Format(PyExc_TypeError, "expected str or None for "
1874+
"'tagname', not %.200s",
1875+
Py_TYPE(tagname)->tp_name);
1876+
}
18701877
m_obj->tagname = PyUnicode_AsWideCharString(tagname, NULL);
18711878
if (m_obj->tagname == NULL) {
18721879
Py_DECREF(m_obj);

0 commit comments

Comments
 (0)