Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Doc/c-api/arg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,28 @@ API Functions
}
.. c:function:: int PyArg_ParseArray(PyObject *const *args, Py_ssize_t nargs, const char *format, ...)
Parse the parameters of a function that takes only array parameters into
local variables (that is, a function using the :c:macro:`METH_FASTCALL`
calling convention).
Returns true on success; on failure, it returns false and raises the
appropriate exception.
.. versionadded:: next
.. c:function:: int PyArg_ParseArrayAndKeywords(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, const char *format, const char * const *kwlist, ...)
Parse the parameters of a function that takes both array and keyword
parameters into local variables (that is, a function using the
:c:macro:`METH_FASTCALL` ``|`` :c:macro:`METH_KEYWORDS` calling convention).
Returns true on success; on failure, it returns false and raises the
appropriate exception.
.. versionadded:: next
.. c:function:: int PyArg_UnpackTuple(PyObject *args, const char *name, Py_ssize_t min, Py_ssize_t max, ...)
A simpler form of parameter retrieval which does not use a format string to
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,11 @@ C API changes
New features
------------

* Add :c:func:`PyArg_ParseArray` and :c:func:`PyArg_ParseArrayAndKeywords`
functions to parse arguments of functions using the :c:macro:`METH_FASTCALL`
calling convention.
(Contributed by Victor Stinner in :gh:`144175`.)

* Add the following functions for the new :class:`frozendict` type:

* :c:func:`PyAnyDict_Check`
Expand Down
13 changes: 13 additions & 0 deletions Include/cpython/modsupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
# error "this header file must not be included directly"
#endif

PyAPI_FUNC(int) PyArg_ParseArray(
PyObject *const *args,
Py_ssize_t nargs,
const char *format,
...);
PyAPI_FUNC(int) PyArg_ParseArrayAndKeywords(
PyObject *const *args,
Py_ssize_t nargs,
PyObject *kwnames,
const char *format,
const char * const *kwlist,
...);

// A data structure that can be used to run initialization code once in a
// thread-safe manner. The C++11 equivalent is std::call_once.
typedef struct {
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_capi/test_modsupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,22 @@ def test_negative_freethreading(self, modname, minor, build):
msg = "only compatible with free-threaded CPython"
with self.assertRaisesRegex(ImportError, msg):
_testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)


class TestModsupport(unittest.TestCase):
def test_pyarg_parsearray(self):
func = _testcapi.pyarg_parsearray
self.assertEqual(func(1, 2), (1, 2, 0))
self.assertEqual(func(1, 2, 3), (1, 2, 3))
self.assertRaises(TypeError, func, 1)
self.assertRaises(TypeError, func, "str", 2)

def test_funcandkeywords(self):
func = _testcapi.pyarg_parsearrayandkeywords
self.assertEqual(func(1, 2), (1, 2, 0))
self.assertEqual(func(1, 2, 3), (1, 2, 3))
self.assertEqual(func(1, b=2), (1, 2, 0))
self.assertEqual(func(1, b=2, c=3), (1, 2, 3))
self.assertRaises(TypeError, func, 1)
self.assertRaises(TypeError, func, "str", 2)
self.assertRaises(TypeError, func, 1, z=2)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`PyArg_ParseArray` and :c:func:`PyArg_ParseArrayAndKeywords`
functions to parse arguments of functions using the :c:macro:`METH_FASTCALL`
calling convention. Patch by Victor Stinner.
28 changes: 28 additions & 0 deletions Modules/_testcapi/modsupport.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,36 @@ pyabiinfo_check(PyObject *Py_UNUSED(module), PyObject *args)
Py_RETURN_NONE;
}

static PyObject *
pyarg_parsearray(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
{
int a, b, c = 0;
if (!PyArg_ParseArray(args, nargs, "ii|i", &a, &b, &c)) {
return NULL;
}
return Py_BuildValue("iii", a, b, c);
}

static PyObject *
pyarg_parsearrayandkeywords(PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames)
{
int a, b, c = 0;
const char *kwlist[] = {"a", "b", "c", NULL};
if (!PyArg_ParseArrayAndKeywords(args, nargs, kwnames,
"ii|i", kwlist,
&a, &b, &c)) {
return NULL;
}
return Py_BuildValue("iii", a, b, c);
}

static PyMethodDef TestMethods[] = {
{"pyabiinfo_check", pyabiinfo_check, METH_VARARGS},
{"pyarg_parsearray", _PyCFunction_CAST(pyarg_parsearray), METH_FASTCALL},
{"pyarg_parsearrayandkeywords",
_PyCFunction_CAST(pyarg_parsearrayandkeywords),
METH_FASTCALL | METH_KEYWORDS},
{NULL},
};

Expand Down
128 changes: 114 additions & 14 deletions Python/getargs.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,15 @@ static const char *convertsimple(PyObject *, const char **, va_list *, int,
static Py_ssize_t convertbuffer(PyObject *, const void **p, const char **);
static int getbuffer(PyObject *, Py_buffer *, const char**);

static int vgetargskeywords(PyObject *, PyObject *,
const char *, const char * const *, va_list *, int);
static int
vgetargskeywords(PyObject *args, PyObject *kwargs,
const char *format, const char * const *kwlist,
va_list *p_va, int flags);
static int
vgetargskeywords_impl(PyObject *const *args, Py_ssize_t nargs,
PyObject *kwargs, PyObject *kwnames,
const char *format, const char * const *kwlist,
va_list *p_va, int flags);
static int vgetargskeywordsfast(PyObject *, PyObject *,
struct _PyArg_Parser *, va_list *, int);
static int vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
Expand Down Expand Up @@ -129,6 +136,40 @@ _PyArg_ParseStack(PyObject *const *args, Py_ssize_t nargs, const char *format, .
return retval;
}

int
PyArg_ParseArray(PyObject *const *args, Py_ssize_t nargs, const char *format, ...)
{
va_list va;
va_start(va, format);
int retval = vgetargs1_impl(NULL, args, nargs, format, &va, 0);
va_end(va);
return retval;
}

int
PyArg_ParseArrayAndKeywords(PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames,
const char *format,
const char * const *kwlist, ...)
{
if ((args == NULL && nargs != 0) ||
(kwnames != NULL && !PyTuple_Check(kwnames)) ||
format == NULL ||
kwlist == NULL)
{
PyErr_BadInternalCall();
return 0;
}

va_list va;
va_start(va, kwlist);
int retval = vgetargskeywords_impl(args, nargs, NULL, kwnames, format,
kwlist, &va, 0);
va_end(va);
return retval;
}


int
PyArg_VaParse(PyObject *args, const char *format, va_list va)
{
Expand Down Expand Up @@ -1612,11 +1653,27 @@ PyArg_ValidateKeywordArguments(PyObject *kwargs)
static PyObject *
new_kwtuple(const char * const *keywords, int total, int pos);

static PyObject*
find_keyword_str(PyObject *kwnames, PyObject *const *kwstack, const char *key)
{
Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwnames);
for (Py_ssize_t i = 0; i < nkwargs; i++) {
PyObject *kwname = PyTuple_GET_ITEM(kwnames, i);
assert(PyUnicode_Check(kwname));
if (PyUnicode_EqualToUTF8(kwname, key)) {
return Py_NewRef(kwstack[i]);
}
}
return NULL;
}

#define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':')

static int
vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
const char * const *kwlist, va_list *p_va, int flags)
vgetargskeywords_impl(PyObject *const *args, Py_ssize_t nargs,
PyObject *kwargs, PyObject *kwnames,
const char *format, const char * const *kwlist,
va_list *p_va, int flags)
{
char msgbuf[512];
int levels[32];
Expand All @@ -1625,16 +1682,18 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
int max = INT_MAX;
int i, pos, len;
int skip = 0;
Py_ssize_t nargs, nkwargs;
Py_ssize_t nkwargs;
freelistentry_t static_entries[STATIC_FREELIST_ENTRIES];
freelist_t freelist;
PyObject * const *kwstack = NULL;

freelist.entries = static_entries;
freelist.first_available = 0;
freelist.entries_malloced = 0;

assert(args != NULL && PyTuple_Check(args));
assert(args != NULL || nargs == 0);
assert(kwargs == NULL || PyDict_Check(kwargs));
assert(kwnames == NULL || PyTuple_Check(kwnames));
assert(format != NULL);
assert(kwlist != NULL);
assert(p_va != NULL);
Expand Down Expand Up @@ -1672,8 +1731,16 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
freelist.entries_malloced = 1;
}

nargs = PyTuple_GET_SIZE(args);
nkwargs = (kwargs == NULL) ? 0 : PyDict_GET_SIZE(kwargs);
if (kwargs != NULL) {
nkwargs = PyDict_GET_SIZE(kwargs);
}
else if (kwnames != NULL) {
nkwargs = PyTuple_GET_SIZE(kwnames);
kwstack = args + nargs;
}
else {
nkwargs = 0;
}
if (nargs + nkwargs > len) {
/* Adding "keyword" (when nargs == 0) prevents producing wrong error
messages in some special cases (see bpo-31229). */
Expand Down Expand Up @@ -1757,11 +1824,16 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
if (!skip) {
PyObject *current_arg;
if (i < nargs) {
current_arg = Py_NewRef(PyTuple_GET_ITEM(args, i));
current_arg = Py_NewRef(args[i]);
}
else if (nkwargs && i >= pos) {
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
return cleanreturn(0, &freelist);
if (kwargs != NULL) {
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
return cleanreturn(0, &freelist);
}
}
else {
current_arg = find_keyword_str(kwnames, kwstack, kwlist[i]);
}
if (current_arg) {
--nkwargs;
Expand Down Expand Up @@ -1846,8 +1918,13 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
/* make sure there are no arguments given by name and position */
for (i = pos; i < nargs; i++) {
PyObject *current_arg;
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
return cleanreturn(0, &freelist);
if (kwargs != NULL) {
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
return cleanreturn(0, &freelist);
}
}
else {
current_arg = find_keyword_str(kwnames, kwstack, kwlist[i]);
}
if (current_arg) {
Py_DECREF(current_arg);
Expand All @@ -1863,7 +1940,20 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
}
/* make sure there are no extraneous keyword arguments */
j = 0;
while (PyDict_Next(kwargs, &j, &key, NULL)) {
while (1) {
if (kwargs != NULL) {
if (!PyDict_Next(kwargs, &j, &key, NULL)) {
break;
}
}
else {
if (j >= nkwargs) {
break;
}
key = PyTuple_GET_ITEM(kwnames, j);
j++;
}

int match = 0;
if (!PyUnicode_Check(key)) {
PyErr_SetString(PyExc_TypeError,
Expand Down Expand Up @@ -1921,6 +2011,16 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
return cleanreturn(1, &freelist);
}

static int
vgetargskeywords(PyObject *argstuple, PyObject *kwargs,
const char *format, const char * const *kwlist,
va_list *p_va, int flags)
{
PyObject *const *args = _PyTuple_ITEMS(argstuple);
Py_ssize_t nargs = PyTuple_GET_SIZE(argstuple);
return vgetargskeywords_impl(args, nargs, kwargs, NULL,
format, kwlist, p_va, flags);
}

static int
scan_keywords(const char * const *keywords, int *ptotal, int *pposonly)
Expand Down
Loading