Skip to content

Commit 76f506e

Browse files
Merge branch 'main' into zipimport-namespace-suffix
2 parents d30e809 + 0e4cf9c commit 76f506e

File tree

5 files changed

+153
-32
lines changed

5 files changed

+153
-32
lines changed

Doc/c-api/arg.rst

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,26 @@ Other objects
361361

362362
.. versionadded:: 3.3
363363

364-
``(items)`` (:class:`tuple`) [*matching-items*]
365-
The object must be a Python sequence whose length is the number of format units
364+
``(items)`` (sequence) [*matching-items*]
365+
The object must be a Python sequence (except :class:`str`, :class:`bytes`
366+
or :class:`bytearray`) whose length is the number of format units
366367
in *items*. The C arguments must correspond to the individual format units in
367368
*items*. Format units for sequences may be nested.
368369

370+
If *items* contains format units which store a :ref:`borrowed buffer
371+
<c-arg-borrowed-buffer>` (``s``, ``s#``, ``z``, ``z#``, ``y``, or ``y#``)
372+
or a :term:`borrowed reference` (``S``, ``Y``, ``U``, ``O``, or ``O!``),
373+
the object must be a Python tuple.
374+
The *converter* for the ``O&`` format unit in *items* must not store
375+
a borrowed buffer or a borrowed reference.
376+
377+
.. versionchanged:: next
378+
:class:`str` and :class:`bytearray` objects no longer accepted as a sequence.
379+
380+
.. deprecated:: next
381+
Non-tuple sequences are deprecated if *items* contains format units
382+
which store a borrowed buffer or a borrowed reference.
383+
369384
``unit?`` (anything or ``None``) [*matching-variable(s)*]
370385
``?`` modifies the behavior of the preceding format unit.
371386
The C variable(s) corresponding to that parameter should be initialized

Doc/whatsnew/3.14.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,13 @@ Deprecated
19431943
:c:macro:`!isfinite` available from :file:`math.h`
19441944
since C99. (Contributed by Sergey B Kirpichev in :gh:`119613`.)
19451945

1946+
* Non-tuple sequences are deprecated as argument for the ``(items)``
1947+
format unit in :c:func:`PyArg_ParseTuple` and other
1948+
:ref:`argument parsing <arg-parsing>` functions if *items* contains
1949+
format units which store a :ref:`borrowed buffer <c-arg-borrowed-buffer>`
1950+
or a :term:`borrowed reference`.
1951+
(Contributed by Serhiy Storchaka in :gh:`50333`.)
1952+
19461953
* The previously undocumented function :c:func:`PySequence_In` is :term:`soft deprecated`.
19471954
Use :c:func:`PySequence_Contains` instead.
19481955
(Contributed by Yuki Kobayashi in :gh:`127896`.)

Lib/test/test_capi/test_getargs.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363

6464
NULL = None
6565

66+
class CustomError(Exception):
67+
pass
68+
6669
class Index:
6770
def __index__(self):
6871
return 99
@@ -586,13 +589,13 @@ def test_tuple(self):
586589
ret = getargs_tuple(1, (2, 3))
587590
self.assertEqual(ret, (1,2,3))
588591

589-
# make sure invalid tuple arguments are handled correctly
590-
class seq:
592+
# make sure invalid sequence arguments are handled correctly
593+
class TestSeq:
591594
def __len__(self):
592595
return 2
593596
def __getitem__(self, n):
594-
raise ValueError
595-
self.assertRaises(TypeError, getargs_tuple, 1, seq())
597+
raise CustomError
598+
self.assertRaises(CustomError, getargs_tuple, 1, TestSeq())
596599

597600
class Keywords_TestCase(unittest.TestCase):
598601
def test_kwargs(self):
@@ -1320,33 +1323,69 @@ def test_nonascii_keywords(self):
13201323
f"this function got an unexpected keyword argument '{name2}'"):
13211324
parse((), {name2: 1, name3: 2}, '|OO', [name, name3])
13221325

1323-
def test_nested_tuple(self):
1326+
def test_nested_sequence(self):
13241327
parse = _testcapi.parse_tuple_and_keywords
13251328

13261329
self.assertEqual(parse(((1, 2, 3),), {}, '(OOO)', ['a']), (1, 2, 3))
13271330
self.assertEqual(parse((1, (2, 3), 4), {}, 'O(OO)O', ['a', 'b', 'c']),
13281331
(1, 2, 3, 4))
13291332
parse(((1, 2, 3),), {}, '(iii)', ['a'])
1333+
parse(([1, 2, 3],), {}, '(iii)', ['a'])
13301334

13311335
with self.assertRaisesRegex(TypeError,
1332-
"argument 1 must be sequence of length 2, not 3"):
1336+
"argument 1 must be tuple of length 2, not 3"):
13331337
parse(((1, 2, 3),), {}, '(ii)', ['a'])
13341338
with self.assertRaisesRegex(TypeError,
1335-
"argument 1 must be sequence of length 2, not 1"):
1339+
"argument 1 must be tuple of length 2, not 1"):
13361340
parse(((1,),), {}, '(ii)', ['a'])
13371341
with self.assertRaisesRegex(TypeError,
1338-
"argument 1 must be 2-item sequence, not int"):
1342+
"argument 1 must be sequence of length 2, not 3"):
1343+
parse(([1, 2, 3],), {}, '(ii)', ['a'])
1344+
with self.assertRaisesRegex(TypeError,
1345+
"argument 1 must be sequence of length 2, not 1"):
1346+
parse(([1,],), {}, '(ii)', ['a'])
1347+
with self.assertRaisesRegex(TypeError,
1348+
"argument 1 must be 2-item tuple, not int"):
13391349
parse((1,), {}, '(ii)', ['a'])
13401350
with self.assertRaisesRegex(TypeError,
1341-
"argument 1 must be 2-item sequence, not bytes"):
1351+
"argument 1 must be 2-item tuple, not None$"):
1352+
parse((None,), {}, '(ii)', ['a'])
1353+
with self.assertRaisesRegex(TypeError,
1354+
"argument 1 must be 2-item tuple, not str"):
1355+
parse(('ab',), {}, '(CC)', ['a'])
1356+
with self.assertRaisesRegex(TypeError,
1357+
"argument 1 must be 2-item tuple, not bytes"):
13421358
parse((b'ab',), {}, '(ii)', ['a'])
1359+
with self.assertRaisesRegex(TypeError,
1360+
"argument 1 must be 2-item tuple, not bytearray"):
1361+
parse((bytearray(b'ab'),), {}, '(ii)', ['a'])
1362+
with self.assertRaisesRegex(TypeError,
1363+
"argument 1 must be 2-item tuple, not dict"):
1364+
parse(({},), {}, '(ii)', ['a'])
1365+
1366+
with self.assertWarnsRegex(DeprecationWarning,
1367+
"argument must be 3-item tuple, not list"):
1368+
self.assertEqual(parse(([1, 2, 3],), {}, '(OOO)', ['a']), (1, 2, 3))
1369+
with self.assertWarnsRegex(DeprecationWarning,
1370+
"argument must be 2-item tuple, not list"):
1371+
with self.assertRaisesRegex(TypeError,
1372+
"argument 1 must be tuple of length 2, not 3"):
1373+
parse(([1, 2, 3],), {}, '(OO)', ['a'])
1374+
with self.assertWarnsRegex(DeprecationWarning,
1375+
"argument must be 2-item tuple, not list"):
1376+
with self.assertRaisesRegex(TypeError,
1377+
"argument 1 must be tuple of length 2, not 1"):
1378+
parse(([1,],), {}, '(OO)', ['a'])
13431379

13441380
for f in 'es', 'et', 'es#', 'et#':
13451381
with self.assertRaises(LookupError): # empty encoding ""
13461382
parse((('a',),), {}, '(' + f + ')', ['a'])
13471383
with self.assertRaisesRegex(TypeError,
1348-
"argument 1 must be sequence of length 1, not 0"):
1384+
"argument 1 must be tuple of length 1, not 0"):
13491385
parse(((),), {}, '(' + f + ')', ['a'])
1386+
with self.assertRaisesRegex(TypeError,
1387+
"argument 1 must be sequence of length 1, not 0"):
1388+
parse(([],), {}, '(' + f + ')', ['a'])
13501389

13511390
def test_specific_type_errors(self):
13521391
parse = _testcapi.parse_tuple_and_keywords
@@ -1396,9 +1435,9 @@ def check(format, arg, expected, got='list'):
13961435
check('U?', [], 'str or None')
13971436
check('Y', [], 'bytearray')
13981437
check('Y?', [], 'bytearray or None')
1399-
check('(OO)', 42, '2-item sequence', 'int')
1400-
check('(OO)?', 42, '2-item sequence or None', 'int')
1401-
check('(OO)', (1, 2, 3), 'sequence of length 2', '3')
1438+
check('(OO)', 42, '2-item tuple', 'int')
1439+
check('(OO)?', 42, '2-item tuple or None', 'int')
1440+
check('(OO)', (1, 2, 3), 'tuple of length 2', '3')
14021441

14031442
def test_nullable(self):
14041443
parse = _testcapi.parse_tuple_and_keywords
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Non-tuple sequences are deprecated as argument for the ``(items)`` format
2+
unit in :c:func:`PyArg_ParseTuple` and other :ref:`argument parsing
3+
<arg-parsing>` functions if *items* contains format units which store
4+
a :ref:`borrowed buffer <c-arg-borrowed-buffer>` or
5+
a :term:`borrowed reference`.

Python/getargs.c

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,8 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
469469
int i;
470470
Py_ssize_t len;
471471
bool nullable = false;
472+
int istuple = PyTuple_Check(arg);
473+
int mustbetuple = istuple;
472474

473475
assert(*format == '(');
474476
format++;
@@ -490,8 +492,36 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
490492
}
491493
else if (c == ':' || c == ';' || c == '\0')
492494
break;
493-
else if (level == 0 && Py_ISALPHA(c) && c != 'e')
494-
n++;
495+
else {
496+
if (level == 0 && Py_ISALPHA(c)) {
497+
n++;
498+
}
499+
if (c == 'e' && (*format == 's' || *format == 't')) {
500+
format++;
501+
continue;
502+
}
503+
if (!mustbetuple) {
504+
switch (c) {
505+
case 'y':
506+
case 's':
507+
case 'z':
508+
if (*format != '*') {
509+
mustbetuple = 1;
510+
}
511+
break;
512+
case 'S':
513+
case 'Y':
514+
case 'U':
515+
mustbetuple = 1;
516+
break;
517+
case 'O':
518+
if (*format != '&') {
519+
mustbetuple = 1;
520+
}
521+
break;
522+
}
523+
}
524+
}
495525
}
496526

497527
if (arg == Py_None && nullable) {
@@ -501,43 +531,65 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
501531
}
502532
return msg;
503533
}
504-
if (!PySequence_Check(arg) || PyBytes_Check(arg)) {
534+
if (istuple) {
535+
/* fallthrough */
536+
}
537+
else if (!PySequence_Check(arg) ||
538+
PyUnicode_Check(arg) || PyBytes_Check(arg) || PyByteArray_Check(arg))
539+
{
505540
levels[0] = 0;
506541
PyOS_snprintf(msgbuf, bufsize,
507-
"must be %d-item sequence%s, not %.50s",
542+
"must be %d-item tuple%s, not %.50s",
508543
n,
509544
nullable ? " or None" : "",
510545
arg == Py_None ? "None" : Py_TYPE(arg)->tp_name);
511546
return msgbuf;
512547
}
548+
else {
549+
if (mustbetuple) {
550+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 0,
551+
"argument must be %d-item tuple, not %T", n, arg))
552+
{
553+
return msgbuf;
554+
}
555+
}
556+
len = PySequence_Size(arg);
557+
if (len != n) {
558+
levels[0] = 0;
559+
PyOS_snprintf(msgbuf, bufsize,
560+
"must be %s of length %d, not %zd",
561+
mustbetuple ? "tuple" : "sequence", n, len);
562+
return msgbuf;
563+
}
564+
arg = PySequence_Tuple(arg);
565+
if (arg == NULL) {
566+
return msgbuf;
567+
}
568+
}
513569

514-
len = PySequence_Size(arg);
570+
len = PyTuple_GET_SIZE(arg);
515571
if (len != n) {
516572
levels[0] = 0;
517573
PyOS_snprintf(msgbuf, bufsize,
518-
"must be sequence of length %d, not %zd",
574+
"must be tuple of length %d, not %zd",
519575
n, len);
576+
if (!istuple) {
577+
Py_DECREF(arg);
578+
}
520579
return msgbuf;
521580
}
522581

523582
format = *p_format + 1;
524583
for (i = 0; i < n; i++) {
525584
const char *msg;
526-
PyObject *item;
527-
item = PySequence_GetItem(arg, i);
528-
if (item == NULL) {
529-
PyErr_Clear();
530-
levels[0] = i+1;
531-
levels[1] = 0;
532-
strncpy(msgbuf, "is not retrievable", bufsize);
533-
return msgbuf;
534-
}
585+
PyObject *item = PyTuple_GET_ITEM(arg, i);
535586
msg = convertitem(item, &format, p_va, flags, levels+1,
536587
msgbuf, bufsize, freelist);
537-
/* PySequence_GetItem calls tp->sq_item, which INCREFs */
538-
Py_XDECREF(item);
539588
if (msg != NULL) {
540589
levels[0] = i+1;
590+
if (!istuple) {
591+
Py_DECREF(arg);
592+
}
541593
return msg;
542594
}
543595
}
@@ -547,6 +599,9 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
547599
format++;
548600
}
549601
*p_format = format;
602+
if (!istuple) {
603+
Py_DECREF(arg);
604+
}
550605
return NULL;
551606
}
552607

0 commit comments

Comments
 (0)