Kaydet (Commit) 83a9f486 authored tarafından Larry Hastings's avatar Larry Hastings

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.
üst 2a886412
...@@ -338,6 +338,15 @@ inside nested parentheses. They are: ...@@ -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
......
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): ...@@ -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(): ...@@ -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) ...@@ -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 ...@@ -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[] = { ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment