mirror of
https://github.com/python/cpython.git
synced 2025-11-01 10:45:30 +00:00
Issue #14328: Add keyword-only parameters to PyArg_ParseTupleAndKeywords.
They're optional-only for now (unlike in pure Python) but that's all I needed. The syntax can easily be relaxed if we want to support required keyword-only arguments for extension types in the future.
This commit is contained in:
parent
2a886412ba
commit
83a9f48699
4 changed files with 134 additions and 3 deletions
|
|
@ -338,6 +338,15 @@ inside nested parentheses. They are:
|
||||||
:c:func:`PyArg_ParseTuple` does not touch the contents of the corresponding C
|
:c:func:`PyArg_ParseTuple` does not touch the contents of the corresponding C
|
||||||
variable(s).
|
variable(s).
|
||||||
|
|
||||||
|
``$``
|
||||||
|
:c:func:`PyArg_ParseTupleAndKeywords` only:
|
||||||
|
Indicates that the remaining arguments in the Python argument list are
|
||||||
|
keyword-only. Currently, all keyword-only arguments must also be optional
|
||||||
|
arguments, so ``|`` must always be specified before ``$`` in the format
|
||||||
|
string.
|
||||||
|
|
||||||
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
``:``
|
``:``
|
||||||
The list of format units ends here; the string after the colon is used as the
|
The list of format units ends here; the string after the colon is used as the
|
||||||
function name in error messages (the "associated value" of the exception that
|
function name in error messages (the "associated value" of the exception that
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
from test import support
|
from test import support
|
||||||
from _testcapi import getargs_keywords
|
from _testcapi import getargs_keywords, getargs_keyword_only
|
||||||
|
|
||||||
"""
|
"""
|
||||||
> How about the following counterproposal. This also changes some of
|
> How about the following counterproposal. This also changes some of
|
||||||
|
|
@ -293,6 +293,77 @@ class Keywords_TestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.fail('TypeError should have been raised')
|
self.fail('TypeError should have been raised')
|
||||||
|
|
||||||
|
class KeywordOnly_TestCase(unittest.TestCase):
|
||||||
|
def test_positional_args(self):
|
||||||
|
# using all possible positional args
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(1, 2),
|
||||||
|
(1, 2, -1)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mixed_args(self):
|
||||||
|
# positional and keyword args
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(1, 2, keyword_only=3),
|
||||||
|
(1, 2, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_keyword_args(self):
|
||||||
|
# all keywords
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(required=1, optional=2, keyword_only=3),
|
||||||
|
(1, 2, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_optional_args(self):
|
||||||
|
# missing optional keyword args, skipping tuples
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(required=1, optional=2),
|
||||||
|
(1, 2, -1)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(required=1, keyword_only=3),
|
||||||
|
(1, -1, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_required_args(self):
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(1),
|
||||||
|
(1, -1, -1)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
getargs_keyword_only(required=1),
|
||||||
|
(1, -1, -1)
|
||||||
|
)
|
||||||
|
# required arg missing
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"Required argument 'required' \(pos 1\) not found"):
|
||||||
|
getargs_keyword_only(optional=2)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"Required argument 'required' \(pos 1\) not found"):
|
||||||
|
getargs_keyword_only(keyword_only=3)
|
||||||
|
|
||||||
|
def test_too_many_args(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"Function takes at most 2 positional arguments \(3 given\)"):
|
||||||
|
getargs_keyword_only(1, 2, 3)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"function takes at most 3 arguments \(4 given\)"):
|
||||||
|
getargs_keyword_only(1, 2, 3, keyword_only=5)
|
||||||
|
|
||||||
|
def test_invalid_keyword(self):
|
||||||
|
# extraneous keyword arg
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"'monster' is an invalid keyword argument for this function"):
|
||||||
|
getargs_keyword_only(1, 2, monster=666)
|
||||||
|
|
||||||
|
def test_surrogate_keyword(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"'\udc80' is an invalid keyword argument for this function"):
|
||||||
|
getargs_keyword_only(1, 2, **{'\uDC80': 10})
|
||||||
|
|
||||||
class Bytes_TestCase(unittest.TestCase):
|
class Bytes_TestCase(unittest.TestCase):
|
||||||
def test_c(self):
|
def test_c(self):
|
||||||
from _testcapi import getargs_c
|
from _testcapi import getargs_c
|
||||||
|
|
@ -441,6 +512,7 @@ def test_main():
|
||||||
Unsigned_TestCase,
|
Unsigned_TestCase,
|
||||||
Tuple_TestCase,
|
Tuple_TestCase,
|
||||||
Keywords_TestCase,
|
Keywords_TestCase,
|
||||||
|
KeywordOnly_TestCase,
|
||||||
Bytes_TestCase,
|
Bytes_TestCase,
|
||||||
Unicode_TestCase,
|
Unicode_TestCase,
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -801,7 +801,8 @@ getargs_tuple(PyObject *self, PyObject *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* test PyArg_ParseTupleAndKeywords */
|
/* test PyArg_ParseTupleAndKeywords */
|
||||||
static PyObject *getargs_keywords(PyObject *self, PyObject *args, PyObject *kwargs)
|
static PyObject *
|
||||||
|
getargs_keywords(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
{
|
{
|
||||||
static char *keywords[] = {"arg1","arg2","arg3","arg4","arg5", NULL};
|
static char *keywords[] = {"arg1","arg2","arg3","arg4","arg5", NULL};
|
||||||
static char *fmt="(ii)i|(i(ii))(iii)i";
|
static char *fmt="(ii)i|(i(ii))(iii)i";
|
||||||
|
|
@ -816,6 +817,21 @@ static PyObject *getargs_keywords(PyObject *self, PyObject *args, PyObject *kwar
|
||||||
int_args[5], int_args[6], int_args[7], int_args[8], int_args[9]);
|
int_args[5], int_args[6], int_args[7], int_args[8], int_args[9]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* test PyArg_ParseTupleAndKeywords keyword-only arguments */
|
||||||
|
static PyObject *
|
||||||
|
getargs_keyword_only(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
{
|
||||||
|
static char *keywords[] = {"required", "optional", "keyword_only", NULL};
|
||||||
|
int required = -1;
|
||||||
|
int optional = -1;
|
||||||
|
int keyword_only = -1;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|i$i", keywords,
|
||||||
|
&required, &optional, &keyword_only))
|
||||||
|
return NULL;
|
||||||
|
return Py_BuildValue("iii", required, optional, keyword_only);
|
||||||
|
}
|
||||||
|
|
||||||
/* Functions to call PyArg_ParseTuple with integer format codes,
|
/* Functions to call PyArg_ParseTuple with integer format codes,
|
||||||
and return the result.
|
and return the result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -2400,6 +2416,8 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"getargs_tuple", getargs_tuple, METH_VARARGS},
|
{"getargs_tuple", getargs_tuple, METH_VARARGS},
|
||||||
{"getargs_keywords", (PyCFunction)getargs_keywords,
|
{"getargs_keywords", (PyCFunction)getargs_keywords,
|
||||||
METH_VARARGS|METH_KEYWORDS},
|
METH_VARARGS|METH_KEYWORDS},
|
||||||
|
{"getargs_keyword_only", (PyCFunction)getargs_keyword_only,
|
||||||
|
METH_VARARGS|METH_KEYWORDS},
|
||||||
{"getargs_b", getargs_b, METH_VARARGS},
|
{"getargs_b", getargs_b, METH_VARARGS},
|
||||||
{"getargs_B", getargs_B, METH_VARARGS},
|
{"getargs_B", getargs_B, METH_VARARGS},
|
||||||
{"getargs_h", getargs_h, METH_VARARGS},
|
{"getargs_h", getargs_h, METH_VARARGS},
|
||||||
|
|
|
||||||
|
|
@ -1403,6 +1403,7 @@ vgetargskeywords(PyObject *args, PyObject *keywords, const char *format,
|
||||||
int levels[32];
|
int levels[32];
|
||||||
const char *fname, *msg, *custom_msg, *keyword;
|
const char *fname, *msg, *custom_msg, *keyword;
|
||||||
int min = INT_MAX;
|
int min = INT_MAX;
|
||||||
|
int max = INT_MAX;
|
||||||
int i, len, nargs, nkeywords;
|
int i, len, nargs, nkeywords;
|
||||||
PyObject *current_arg;
|
PyObject *current_arg;
|
||||||
freelist_t freelist = {0, NULL};
|
freelist_t freelist = {0, NULL};
|
||||||
|
|
@ -1452,8 +1453,39 @@ vgetargskeywords(PyObject *args, PyObject *keywords, const char *format,
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
keyword = kwlist[i];
|
keyword = kwlist[i];
|
||||||
if (*format == '|') {
|
if (*format == '|') {
|
||||||
|
if (min != INT_MAX) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"Invalid format string (| specified twice)");
|
||||||
|
return cleanreturn(0, &freelist);
|
||||||
|
}
|
||||||
|
|
||||||
min = i;
|
min = i;
|
||||||
format++;
|
format++;
|
||||||
|
|
||||||
|
if (max != INT_MAX) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"Invalid format string ($ before |)");
|
||||||
|
return cleanreturn(0, &freelist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*format == '$') {
|
||||||
|
if (max != INT_MAX) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"Invalid format string ($ specified twice)");
|
||||||
|
return cleanreturn(0, &freelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
max = i;
|
||||||
|
format++;
|
||||||
|
|
||||||
|
if (max < nargs) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"Function takes %s %d positional arguments"
|
||||||
|
" (%d given)",
|
||||||
|
(min != INT_MAX) ? "at most" : "exactly",
|
||||||
|
max, nargs);
|
||||||
|
return cleanreturn(0, &freelist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (IS_END_OF_FORMAT(*format)) {
|
if (IS_END_OF_FORMAT(*format)) {
|
||||||
PyErr_Format(PyExc_RuntimeError,
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
|
@ -1514,7 +1546,7 @@ vgetargskeywords(PyObject *args, PyObject *keywords, const char *format,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IS_END_OF_FORMAT(*format) && *format != '|') {
|
if (!IS_END_OF_FORMAT(*format) && (*format != '|') && (*format != '$')) {
|
||||||
PyErr_Format(PyExc_RuntimeError,
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
"more argument specifiers than keyword list entries "
|
"more argument specifiers than keyword list entries "
|
||||||
"(remaining format:'%s')", format);
|
"(remaining format:'%s')", format);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue