8. Parsing Python Arguments¶
This section describes how you write functions that accept Python *args and **kwargs
arguments and how to extract PyObject or C fundamental types from them.
8.1. Specifying the Function Arguments¶
Python has a myriad of ways of function arguments and the C API reflects that. Two important features of CPython C functions are:
Declaring them correctly with the right signature and flags.
Parsing the arguments and checking their types.
These are described below.
8.1.1. C Function Declaration¶
The C function signature must be declared correctly depending on the arguments it is expected to work with. Here is a small summary of the required declaration.
In all cases the function has a context which is the first argument.
For free functions the first argument is the module within which the function is declared. This allows you to access other attributes or functions in the module.
For member functions (methods) the first argument is the object within which the function is declared (
self). This allows you to access other properties or functions in the object.
If the argument is unused then the Py_UNUSED can be used to supress a compiler warning or error thus:
static PyObject *
parse_args_kwargs(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs);
8.1.1.1. Setting the ml_flags Field¶
The ml_flags field in PyMethodDef specifies the form of the arguments.
8.1.1.1.1. No Arguments¶
The flags will be METH_NOARGS
The C Function Signature will be
PyObject *PyCFunction(PyObject *self, PyObject *args);The second argument will be
NULL.
8.1.1.1.2. One Argument¶
The flags will be METH_O
The C Function Signature will be
PyObject *PyCFunction(PyObject *self, PyObject *args);The second argument will be the single argument.
8.1.1.1.3. Multiple Positional Arguments¶
The flags will be METH_VARARGS
The C Function Signature will be
PyObject *PyCFunction(PyObject *self, PyObject *args);Second value will be a tuple of arguments.
PyArg_ParseTuple() is used to unpack the arguments.
8.1.1.1.4. Positional and Keyword Arguments¶
The flags will be METH_NOARGS | METH_KEYWORDS
The C Function Signature will be
PyObject *PyCFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kwargs);Second value will be a sequence of arguments, the third the dictionary of arguments.
PyArg_ParseTupleAndKeywords() is used to unpack the arguments.
Documentation:
No arguments, single or multiple arguments: PyCFunction
Multiple argument and keywords PyCFunctionWithKeywords
Note
I don’t cover the use of METH_FASTCALL in this tutorial.
Note
METH_KEYWORDS can only be used in combination with other flags.
8.1.1.1.5. Example¶
A function that takes positional and keyword arguments would be declared as:
static PyObject *
parse_args_kwargs(PyObject *module, PyObject *args, PyObject *kwargs);
And this would be added to the module, say, by using:
static PyMethodDef cParseArgs_methods[] = {
/* ... */
{
"parse_args_kwargs",
(PyCFunction) parse_args_kwargs,
METH_VARARGS | METH_KEYWORDS,
"Documentation for parse_args_kwargs()."
},
/* ... */
{NULL, NULL, 0, NULL} /* Sentinel */
};
8.1.1.2. Parsing the Arguments¶
Once we have the C function correctly declared then the arguments have to parsed according to their types and, if required, converted to C types (so-called “unboxing”). This is done using the PyArg_ParseTuple() and PyArg_ParseTupleAndKeywords() (ignoring “old-style” functions which use PyArg_Parse).
These use formatting strings that can become bewilderingly complex so this tutorial uses examples as an introduction. The reference documentation is excellent: argument parsing and building values.
Warning
Error messages can be slightly misleading as the argument index is the C index not the Python index.
For example a C function using METH_VARARGS declared as:
static PyObject *parse_args(PyObject *module, PyObject *args);
Which expects the Python args[0] to be a bytes object and the Python args[1] to be an integer by using:
PyArg_ParseTuple(args, "Si", &arg0, &arg1)
Calling this from Python with parse_args(21, 22) will give:
TypeError: argument 1 must be bytes, not int.
The “1” here refers to the C index not the Python index. The correct call would be to change the 0th Python argument to:
cParseArgs.parse_args(b'21', 22)
Also consider the signature:
def parse_args(a: bytes, b: int, c: str = '') -> int:
Called with parse_args(b'bytes', '456') gives the error:
"'str' object cannot be interpreted as an integer"
without specifying which argument it is referring to.
These examples are in src/cpy/cParseArgs.c and their tests are in tests/unit/test_c_parse_args.py.
8.1.1.3. No Arguments¶
The simplest form is a global function in a module that takes no arguments at all:
static PyObject *parse_no_args(PyObject *Py_UNUSED(module)) {
/* Your code here...*/
Py_RETURN_NONE;
}
This function is added to the module methods with the METH_NOARGS value.
The Python interpreter will raise a TypeError on any arguments are offered to the function.
static PyMethodDef cParseArgs_methods[] = {
/* Other functions here... */
{
"parse_no_args",
(PyCFunction)parse_no_args,
METH_NOARGS,
"No arguments."
},
/* Other functions here... */
{NULL, NULL, 0, NULL} /* Sentinel */
};
8.1.1.4. One Argument¶
There is no parsing required here, a single PyObject is expected:
static PyObject *parse_one_arg(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(arg)) {
/* arg as a borrowed reference and the general rule is that you Py_INCREF them
* whilst you have an interest in them.
*/
// Py_INCREF(arg);
/* Your code here...*/
/* If we were to treat arg as a borrowed reference and had Py_INCREF'd above we
* should do this. See below. */
// Py_DECREF(arg);
Py_RETURN_NONE;
}
This function can be added to the module with the METH_O flag:
static PyMethodDef cParseArgs_methods[] = {
/* Other functions here... */
{
"parse_one_arg",
(PyCFunction) parse_one_arg,
METH_O,
"One argument."
},
/* Other functions here... */
{NULL, NULL, 0, NULL} /* Sentinel */
};
8.1.1.4.1. Arguments as Borrowed References¶
There is some subtlety here as indicated by the comments. *arg is not our reference, it is a borrowed reference
so why don’t we increment it at the beginning of this function and decrement it at the end?
After all we are trying to protect against calling into some malicious/badly written code that could hurt us.
For example:
static PyObject *foo(PyObject *module, PyObject *arg) {
/* arg has a minimum recount of 1. */
call_malicious_code_that_decrefs_by_one_this_argument(arg);
/* arg potentially could have had a ref count of 0 and been deallocated. */
/* ... */
/* So now doing something with arg could be undefined. */
}
A solution would be, since arg is a ‘borrowed’ reference and borrowed references should always be incremented
whilst in use and decremented when done with.
This would suggest the following:
static PyObject *foo(PyObject *module, PyObject *arg) {
/* arg has a minimum recount of 1. */
Py_INCREF(arg);
/* arg now has a minimum recount of 2. */
call_malicious_code_that_decrefs_by_one_this_argument(arg);
/* arg can not have a ref count of 0 so is safe to use. */
/* Use arg to your hearts content... */
/* Do a matching decref. */
Py_DECREF(arg);
/* But now arg could have had a ref count of 0 so is unsafe to use by the caller. */
}
But now we have just pushed the burden onto our caller.
They created arg and passed it to us in good faith and whilst we have protected ourselves have not protected the
caller and they can fail unexpectedly.
So it is best to fail fast, an near the error site, that dastardly
call_malicious_code_that_decrefs_by_one_this_argument().
Side note: Of course this does not protect you from malicious/badly written code that decrements by more than one :-)
8.1.1.5. Variable Number of Arguments¶
The function will be called with two arguments, the module and a PyTupleObject that contains a tuple of arguments.
You can either parse this list yourself or use a helper method to parse it into Python and C types.
In the following code we are expecting a bytes object, an integer and an optional string whose default value is ‘default_string’. For demonstration purposes, this returns the same three arguments. In Python the equivalent function signature would be:
def parse_args(a: bytes, b: int, c: str = 'default_string') \
-> typing.Tuple[bytes, int, str]:
Here is the C code, note the string that describes the argument types passed to PyArg_ParseTuple, if these types
are not present a ValueError will be set.
static PyObject *parse_args(PyObject *Py_UNUSED(module), PyObject *args) {
PyObject *arg_0 = NULL;
int arg_1;
char *arg_2 = "default_string";
if (!PyArg_ParseTuple(args, "Si|s", &arg_0, &arg_1, &arg_2)) {
return NULL;
}
/* Your code here...*/
return Py_BuildValue("Ois", arg_0, arg_1, arg_2);
}
This function can be added to the module with the METH_VARARGS flag:
static PyMethodDef cParseArgs_methods[] = {
/* Other functions here... */
{
"parse_args",
(PyCFunction) parse_args,
METH_VARARGS,
"parse_args() documentation"
},
/* Other functions here... */
{NULL, NULL, 0, NULL} /* Sentinel */
};
This code can be seen in src/cpy/cParseArgs.c.
It is tested in tests.unit.test_c_parse_args.test_parse_args.
Failure modes, when the wrong arguments are passed are tested in tests.unit.test_c_parse_args.test_parse_args_raises.
Note the wide variety of error messages that are obtained.
8.1.1.6. Variable Number of Arguments and Keyword Arguments¶
The function will be called with three arguments, the module, a PyTupleObject that contains a tuple of arguments
and a PyDictObject that contains a dictionary of keyword arguments.
The keyword arguments can be NULL if there are no keyword arguments.
You can either parse these yourself or use a helper method to parse it into Python and C types.
In the following code we are expecting a sequence and an optional integer count defaulting to 1. It returns the sequence repeated count times. In Python the equivalent function declaration would be:
def parse_args_kwargs(sequence=typing.Sequence[typing.Any], count: = 1) \
-> typing.Sequence[typing.Any]:
return sequence * count
Here is the C code, note the string "O|i" that describes the argument types passed to
PyArg_ParseTupleAndKeywords, if these types are not present a TypeError will be set.
1static PyObject *
2parse_args_kwargs(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs) {
3 PyObject *ret = NULL;
4 PyObject *py_sequence = NULL;
5 int count = 1; /* Default. */
6 static char *kwlist[] = {
7 "sequence", /* A sequence object, str, list, tuple etc. */
8 "count", /* Python int converted to a C int. */
9 NULL,
10 };
11
12 if (!PyArg_ParseTupleAndKeywords(
13 args, kwargs, "O|i", kwlist, &py_sequence, &count
14 )) {
15 goto except;
16 }
17
18 /* Your code here...*/
19
20 ret = PySequence_Repeat(py_sequence, count);
21 if (ret == NULL) {
22 goto except;
23 }
24 assert(!PyErr_Occurred());
25 goto finally;
26except:
27 assert(PyErr_Occurred());
28 Py_XDECREF(ret);
29 ret = NULL;
30finally:
31 return ret;
32}
This function can be added to the module with both the METH_VARARGS and METH_KEYWORDS flags set:
static PyMethodDef cParseArgs_methods[] = {
/* Other functions here... */
{
"parse_args_kwargs",
(PyCFunction) parse_args_kwargs,
METH_VARARGS | METH_KEYWORDS,
"parse_args_kwargs() documentation"
},
/* Other functions here... */
{NULL, NULL, 0, NULL} /* Sentinel */
};
This code can be seen in src/cpy/cParseArgs.c.
It is tested in tests.unit.test_c_parse_args.test_parse_args_kwargs which shows the variety of ways this can be
called.
Failure modes, when the wrong arguments are passed are tested in
tests.unit.test_c_parse_args.test_parse_args_kwargs_raises.
All arguments are keyword arguments so this function can be called in a number of ways, all of the following are equivalent:
cParseArgs.parse_args_kwargs([1, 2, 3], 2)
cParseArgs.parse_args_kwargs([1, 2, 3], count=2)
cParseArgs.parse_args_kwargs(sequence=[1, 2, 3], count=2)
8.1.1.6.1. Keyword Arguments and C++11¶
C++11 compilers warn when creating non-const char* from string literals as we have done with the keyword array
above.
The solution is to declare these const char* however PyArg_ParseTupleAndKeywords() expects a char **.
The solution is to cast away const in the call:
/* ... */
static const char *kwlist[] = { "foo", "bar", "baz", NULL };
if (! PyArg_ParseTupleAndKeywords(args, kwds, "OOO",
const_cast<char**>(kwlist),
&foo, &bar, &baz)) {
return NULL;
}
/* ... */
8.1.1.7. Default String and Bytes Arguments¶
The recommended way to accept binary data is to parse the argument using the "y*" formatting string and supply
a Py_Buffer argument.
This also applies to strings, using the "s*" formatting string, where they might contain '\0' characters.
Typically this would be done with C code such as this:
Py_buffer arg;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y*", kwlist, &arg)) {
assert(PyErr_Occurred());
return NULL;
}
/* arg.buf has the byte data of length arg.len */
However this will likely segfault if it is used as a default argument using "|y*" formatting string as the
Py_Buffer is uninitialized.
Here is the fix for using a default value of b'':
Py_buffer arg;
arg.buf = NULL;
arg.len = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|y*", kwlist, &arg)) {
assert(PyErr_Occurred());
return NULL;
}
For a different default value, say b'default', then this will work.
The Python signature is:
def parse_default_bytes_object(b: bytes = b"default") -> bytes:
The complete C code is:
static PyObject *
parse_default_bytes_object(PyObject *Py_UNUSED(module), PyObject *args,
PyObject *kwargs) {
static const char *arg_default = "default";
Py_buffer arg;
arg.buf = (void *)arg_default;
arg.len = strlen(arg_default);
static char *kwlist[] = {
"b",
NULL,
};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|y*", kwlist, &arg)) {
assert(PyErr_Occurred());
return NULL;
}
return Py_BuildValue("y#", arg.buf, arg.len);
}
See tests/unit/test_c_parse_args.py for some Python uses of this code.
8.1.1.8. Positional Only and Keyword Only Arguments¶
This section shows how to achieve positional only and keyword only arguments in a C extension. These are described in the Python documentation for Special parameters Specifically positional only parameters and keyword only arguments.
Suppose we want the functional equivalent of the Python function signature (reproducing https://docs.python.org/3/tutorial/controlflow.html#special-parameters ):
def parse_pos_only_kwd_only(pos1: str, pos2: int, /, pos_or_kwd: bytes, *,
kwd1: float = 256.0, kwd2: int = -421):
return None
This is achieved by combining two techniques:
Positional only: The strings in the
*kwlistpassed to PyArg_ParseTupleAndKeywords() are empty.Keyword only: The formatting string passed to PyArg_ParseTupleAndKeywords() uses the
'$'character.
A function using either positional only or keyword only arguments must use the flags METH_VARARGS | METH_KEYWORDS
and uses PyArg_ParseTupleAndKeywords(). Currently, all keyword-only arguments must also be optional arguments, so '|' must always be
specified before '$' in the format string.
Here is the C code:
1static PyObject *
2parse_pos_only_kwd_only(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs) {
3 /* Arguments, first three are required. */
4 Py_buffer pos1;
5 int pos2;
6 Py_buffer pos_or_kwd;
7 /* Last two are optional. */
8 double kwd1 = 256.0;
9 int kwd2 = -421;
10 static char *kwlist[] = {
11 "", /* pos1 is positional only. */
12 "", /* pos2 is positional only. */
13 "pos_or_kwd", /* pos_or_kwd can be positional or keyword argument. */
14 /* NOTE: As '$' is in format string the next to are keyword only. */
15 "kwd1", /* kwd1 is keyword only argument. */
16 "kwd2", /* kwd2 is keyword only argument. */
17 NULL,
18 };
19
20 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s*iy*|$di", kwlist, &pos1, &pos2,
21 &pos_or_kwd, &kwd1, &kwd2)) {
22 assert(PyErr_Occurred());
23 return NULL;
24 }
25 /* Return the parsed arguments.
26 * NOTE the asymmetry between "s*iy*|$di" above and "s#iy#di" here. */
27 return Py_BuildValue("s#iy#di", pos1.buf, pos1.len, pos2, pos_or_kwd.buf,
28 pos_or_kwd.len, kwd1, kwd2);
29}
8.1.1.9. Parsing Arguments With Functional Conversion to C¶
Often you want to convert a Python argument to a C value(s) in a way that is not covered by the format strings provided by the Python C API. To do this you can provide a special conversion function in C and give it to PyArg_ParseTuple() or PyArg_ParseTupleAndKeywords().
In this example we are expecting the Python argument to be a list of integers and we want the sum of them as
a C long. First create a C function that takes a Python list, checks it and sums the values.
The function returns 1 on success or 0 on error and, in that case, and exception is expected to be set.
On success the result will be written into the opaque pointer, here called address:
1int sum_list_of_longs(PyObject *list_longs, void *address) {
2 PyObject *item = NULL;
3
4 /* Note: PyList_Check allows sub-types. */
5 if (! list_longs || ! PyList_Check(list_longs)) {
6 PyErr_Format(PyExc_TypeError,
7 "check_list_of_longs(): First argument is not a list"
8 );
9 return 0;
10 }
11 long result = 0L;
12 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(list_longs); ++i) {
13 item = PyList_GetItem(list_longs, i);
14 if (!PyLong_CheckExact(item)) {
15 PyErr_Format(PyExc_TypeError,
16 "check_list_of_longs(): Item %d is not a Python integer.", i
17 );
18 return 0;
19 }
20 /* PyLong_AsLong() must always succeed because of check above. */
21 result += PyLong_AsLong(item);
22 }
23 long *p_long = (long *) address;
24 *p_long = result;
25 return 1; /* Success. */
26}
Now we can pass this function to PyArg_ParseTuple with the "O&" formatting string that takes two arguments,
the Python list and the C conversion function. On success PyArg_ParseTuple writes the value to the target,
result.
In this case the function just returns the sum of the integers in the list. Here is the C code.
1static PyObject *
2parse_args_with_function_conversion_to_c(PyObject *Py_UNUSED(module), PyObject *args) {
3 PyObject *ret = NULL;
4 long result;
5
6 if (!PyArg_ParseTuple(args, "O&", sum_list_of_longs, &result)) {
7 /* NOTE: If check_list_of_numbers() returns 0 an error should be set. */
8 assert(PyErr_Occurred());
9 goto except;
10 }
11
12 /* Your code here...*/
13 ret = PyLong_FromLong(result);
14 if (ret == NULL) {
15 goto except;
16 }
17 assert(!PyErr_Occurred());
18 goto finally;
19except:
20 assert(PyErr_Occurred());
21 Py_XDECREF(ret);
22 ret = NULL;
23finally:
24 return ret;
25}
If the arguments default to some C fundamental type the code above is fine. However if the mutable arguments default to Python objects then a little more work is needed.
Note
See Handling Default Arguments for a way of simplifying this with C++.
Here is a function that has an object and as default argument as a list, appends the object to the list and returns the list. The Python signature is:
def function(arg_0, arg_1=[]):
arg_1.append(arg_0)
return arg_1
The first argument is immutable, the second is mutable. We need to mimic the well known behaviour of Python with mutable arguments where default arguments are evaluated once only at function definition time and then becomes a (mutable) property of the function.
For example:
>>> function(1)
[1]
>>> function(2)
[1, 2]
>>> my_list = []
>>> f(10, my_list)
[10]
>>> f(10)
[1, 2, 10]
In C we can get this behaviour by treating the mutable argument as static, the immutable argument does not need to
be static but it will do no harm if it is (if non-static it will have to be initialised on every function call).
My advice: Always make all PyObject* references to default arguments static.
So first we declare a static PyObject* for each default argument:
1/** Parse the args where we are simulating mutable default of an empty list.
2 *
3 * This is equivalent to:
4 *
5 * def parse_args_with_mutable_defaults_macro_helper(obj, default_list=[]):
6 * default_list.append(obj)
7 * return default_list
8 *
9 * See also parse_args_with_mutable_defaults_macro_helper() in cParseArgsHelper.cpp
10 * This adds the object to the list and returns None.
11 *
12 * This imitates the Python way of handling defaults.
13 */
14static PyObject *parse_args_with_mutable_defaults(PyObject *Py_UNUSED(module),
15 PyObject *args) {
16 PyObject *ret = NULL;
17 /* Pointers to the non-default argument, initialised by PyArg_ParseTuple below. */
18 PyObject *arg_0 = NULL;
19 /* Pointers to the default argument, initialised below. */
20 static PyObject *arg_1_default = NULL;
21 /* Set defaults for argument 1. */
22 if (!arg_1_default) {
23 arg_1_default = PyList_New(0);
24 }
Then we declare a PyObject* for each argument that will either reference the default or the passed in argument.
It is important that these pyObjArg_... pointers are NULL so that we can subsequently detect if
PyArg_ParseTuple has set them non-NULL.
/* This pointer is the one we use in the body of the function, it
* either points at the supplied argument or the default (static) argument.
*/
PyObject *arg_1 = NULL;
Now parse the given arguments to see what, if anything, is there.
PyArg_ParseTuple will set each working pointer non-NULL if the argument is present.
As we set the working pointers NULL prior to this call we can now tell if any argument is present.
if (!PyArg_ParseTuple(args, "O|O", &arg_0, &arg_1)) {
goto except;
}
Now switch our working pointers to the default argument if no argument is given. We also treat these as “borrowed” references regardless of whether they are default or supplied so increment the refcount (we must decrement the refcount when done).
/* If optional argument absent then switch to defaults. */
if (!arg_1) {
arg_1 = arg_1_default;
}
Now write the main body of your function and that must be followed by this clean up code:
/* Your code here...*/
/* Append the first argument to the second.
* PyList_Append increments the reference count of arg_0. */
if (PyList_Append(arg_1, arg_0)) {
PyErr_SetString(PyExc_RuntimeError, "Can not append to list!");
goto except;
}
/* Success. */
assert(!PyErr_Occurred());
/* Increments the default or the given argument. */
Py_INCREF(arg_1);
ret = arg_1;
goto finally;
Now the two blocks except and finally.
except:
assert(PyErr_Occurred());
Py_XDECREF(ret);
ret = NULL;
finally:
return ret;
}
Here is the complete C code:
1/** Parse the args where we are simulating mutable default of an empty list.
2 *
3 * This is equivalent to:
4 *
5 * def parse_args_with_mutable_defaults_macro_helper(obj, default_list=[]):
6 * default_list.append(obj)
7 * return default_list
8 *
9 * See also parse_args_with_mutable_defaults_macro_helper() in cParseArgsHelper.cpp
10 * This adds the object to the list and returns None.
11 *
12 * This imitates the Python way of handling defaults.
13 */
14static PyObject *parse_args_with_mutable_defaults(PyObject *Py_UNUSED(module),
15 PyObject *args) {
16 PyObject *ret = NULL;
17 /* Pointers to the non-default argument, initialised by PyArg_ParseTuple below. */
18 PyObject *arg_0 = NULL;
19 /* Pointers to the default argument, initialised below. */
20 static PyObject *arg_1_default = NULL;
21 /* Set defaults for argument 1. */
22 if (!arg_1_default) {
23 arg_1_default = PyList_New(0);
24 }
25 /* This pointer is the one we use in the body of the function, it
26 * either points at the supplied argument or the default (static) argument.
27 */
28 PyObject *arg_1 = NULL;
29
30 if (!PyArg_ParseTuple(args, "O|O", &arg_0, &arg_1)) {
31 goto except;
32 }
33 /* If optional argument absent then switch to defaults. */
34 if (!arg_1) {
35 arg_1 = arg_1_default;
36 }
37
38 /* Your code here...*/
39
40 /* Append the first argument to the second.
41 * PyList_Append increments the reference count of arg_0. */
42 if (PyList_Append(arg_1, arg_0)) {
43 PyErr_SetString(PyExc_RuntimeError, "Can not append to list!");
44 goto except;
45 }
46
47 /* Success. */
48 assert(!PyErr_Occurred());
49 /* Increments the default or the given argument. */
50 Py_INCREF(arg_1);
51 ret = arg_1;
52 goto finally;
53except:
54 assert(PyErr_Occurred());
55 Py_XDECREF(ret);
56 ret = NULL;
57finally:
58 return ret;
59}
The code can be found in src/cpy/ParseArgs/cParseArgs.c.
Tests are in test_parse_args_with_mutable_defaults() in tests/unit/test_c_parse_args.py
Some macros can make this easier.
These are in src/cpy/ParseArgs/cParseArgsHelper.cpp.
Firstly a macro to declare the static default object:
#define PY_DEFAULT_ARGUMENT_INIT(name, value, ret) \
PyObject *name = NULL; \
static PyObject *default_##name = NULL; \
if (! default_##name) { \
default_##name = value; \
if (! default_##name) { \
PyErr_SetString( \
PyExc_RuntimeError, \
"Can not create default value for " #name \
); \
return ret; \
} \
}
Warning
When using this macro in a source file then make sure each given “name” argument is unique within the translation unit.
And a macro to set it:
#define PY_DEFAULT_ARGUMENT_SET(name) \
if (! name) { \
name = default_##name; \
}
And a macro to check the type of the argument:
#define PY_DEFAULT_CHECK(name, check_function, type) \
if (!check_function(name)) { \
PyErr_Format( \
PyExc_TypeError, \
#name " must be " #type ", not \"%s\"", \
Py_TYPE(name)->tp_name \
); \
return NULL; \
}
8.1.1.10. Immutable Arguments¶
These can be used thus. This is equivalent to the Python function:
def parse_defaults_with_helper_macro(
encoding_m: str = "utf-8",
the_id_m: int = 1024,
log_interval_m: float = 8.0):
return encoding_m, the_id_m, log_interval_m
Here it is in C:
1static PyObject *
2parse_defaults_with_helper_macro(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwds) {
3 PyObject *ret = NULL;
4 /* Initialise default arguments. Note: these might cause an early return. */
5 PY_DEFAULT_ARGUMENT_INIT(encoding_m, PyUnicode_FromString("utf-8"), NULL);
6 PY_DEFAULT_ARGUMENT_INIT(the_id_m, PyLong_FromLong(DEFAULT_ID), NULL);
7 PY_DEFAULT_ARGUMENT_INIT(log_interval_m, PyFloat_FromDouble(DEFAULT_FLOAT), NULL);
8
9 static const char *kwlist[] = {"encoding", "the_id", "log_interval", NULL};
10 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO",
11 const_cast<char **>(kwlist),
12 &encoding_m, &the_id_m, &log_interval_m)) {
13 goto except;
14 }
15 /*
16 * Assign absent arguments to defaults and increment the reference count.
17 * Don't forget to decrement the reference count before returning!
18 */
19 PY_DEFAULT_ARGUMENT_SET(encoding_m);
20 PY_DEFAULT_ARGUMENT_SET(the_id_m);
21 PY_DEFAULT_ARGUMENT_SET(log_interval_m);
22
23 /* Check the types of the given or default arguments. */
24 PY_DEFAULT_CHECK(encoding_m, PyUnicode_Check, "str");
25 PY_DEFAULT_CHECK(the_id_m, PyLong_Check, "int");
26 PY_DEFAULT_CHECK(log_interval_m, PyFloat_Check, "float");
27
28 /*
29 * Use 'encoding': Python str, 'the_id': C long, 'must_log': C long from here on...
30 */
31
32 /* Py_BuildValue("O") increments the reference count. */
33 ret = Py_BuildValue("OOO", encoding_m, the_id_m, log_interval_m);
34 assert(!PyErr_Occurred());
35 assert(ret);
36 goto finally;
37except:
38 assert(PyErr_Occurred());
39 Py_XDECREF(ret);
40 ret = NULL;
41finally:
42 return ret;
43}
The code is:
Function:
parse_defaults_with_helper_macro()Location:
src/cpy/ParseArgs/cParseArgsHelper.cpp
The tests are in tests/unit/test_c_parse_args_helper.py.
If you are in a C++ environment then the section on Handling Default Arguments can help.
8.1.1.11. Mutable Arguments¶
These macros also work with mutable arguments.
The following C code emulates this Python code:
def parse_mutable_defaults_with_helper_macro(obj, default_list=[]):
default_list.append(obj)
return default_list
Here is the C code:
1/** Parse the args where we are simulating mutable default of an empty list.
2 * This uses the helper macros.
3 *
4 * This is equivalent to:
5 *
6 * def parse_mutable_defaults_with_helper_macro(obj, default_list=[]):
7 * default_list.append(obj)
8 * return default_list
9 *
10 * This adds the object to the list and returns None.
11 *
12 * This imitates the Python way of handling defaults.
13 */
14static PyObject *parse_mutable_defaults_with_helper_macro(PyObject *Py_UNUSED(module),
15 PyObject *args) {
16 PyObject *ret = NULL;
17 /* Pointers to the non-default argument, initialised by PyArg_ParseTuple below. */
18 PyObject *arg_0 = NULL;
19 /* Pointers to the default argument, initialised below. */
20 /* Initialise default arguments. Note: these might cause an early return. */
21 PY_DEFAULT_ARGUMENT_INIT(list_argument_m, PyList_New(0), NULL);
22
23 if (!PyArg_ParseTuple(args, "O|O", &arg_0, &list_argument_m)) {
24 goto except;
25 }
26 /* If optional argument absent then switch to defaults. */
27 PY_DEFAULT_ARGUMENT_SET(list_argument_m);
28 PY_DEFAULT_CHECK(list_argument_m, PyList_Check, "list");
29
30 /* Your code here...*/
31
32 /* Append the first argument to the second.
33 * PyList_Append() increments the refcount of arg_0. */
34 if (PyList_Append(list_argument_m, arg_0)) {
35 PyErr_SetString(PyExc_RuntimeError, "Can not append to list!");
36 goto except;
37 }
38
39 /* Success. */
40 assert(!PyErr_Occurred());
41 /* This increments the default or the given argument. */
42 Py_INCREF(list_argument_m);
43 ret = list_argument_m;
44 goto finally;
45except:
46 assert(PyErr_Occurred());
47 Py_XDECREF(ret);
48 ret = NULL;
49finally:
50 return ret;
51}
Here is some test code from tests/unit/test_c_parse_args_helper.py.
First a test to establish the Python behaviour:
1def test_parse_mutable_defaults_with_helper_macro_python():
2 """A local Python equivalent of cParseArgsHelper.parse_mutable_defaults_with_helper_macro()."""
3
4 def parse_mutable_defaults_with_helper_macro(obj, default_list=[]):
5 default_list.append(obj)
6 return default_list
7
8 result = parse_mutable_defaults_with_helper_macro(1)
9 assert sys.getrefcount(result) == 3
10 assert result == [1, ]
11 result = parse_mutable_defaults_with_helper_macro(2)
12 assert sys.getrefcount(result) == 3
13 assert result == [1, 2]
14 result = parse_mutable_defaults_with_helper_macro(3)
15 assert sys.getrefcount(result) == 3
16 assert result == [1, 2, 3]
17
18 local_list = []
19 assert sys.getrefcount(local_list) == 2
20 assert parse_mutable_defaults_with_helper_macro(10, local_list) == [10]
21 assert sys.getrefcount(local_list) == 2
22 assert parse_mutable_defaults_with_helper_macro(11, local_list) == [10, 11]
23 assert sys.getrefcount(local_list) == 2
24
25 result = parse_mutable_defaults_with_helper_macro(4)
26 assert result == [1, 2, 3, 4]
27 assert sys.getrefcount(result) == 3
Now a similar test to establish the C behaviour:
1from cPyExtPatt import cParseArgsHelper
2
3def test_parse_mutable_defaults_with_helper_macro_c():
4 result = cParseArgsHelper.parse_mutable_defaults_with_helper_macro(1)
5 assert sys.getrefcount(result) == 3
6 assert result == [1, ]
7 result = cParseArgsHelper.parse_mutable_defaults_with_helper_macro(2)
8 assert sys.getrefcount(result) == 3
9 assert result == [1, 2]
10 result = cParseArgsHelper.parse_mutable_defaults_with_helper_macro(3)
11 assert sys.getrefcount(result) == 3
12 assert result == [1, 2, 3]
13
14 local_list = []
15 assert sys.getrefcount(local_list) == 2
16 assert cParseArgsHelper.parse_mutable_defaults_with_helper_macro(10, local_list) == [10]
17 assert sys.getrefcount(local_list) == 2
18 assert cParseArgsHelper.parse_mutable_defaults_with_helper_macro(11, local_list) == [10, 11]
19 assert sys.getrefcount(local_list) == 2
20
21 result = cParseArgsHelper.parse_mutable_defaults_with_helper_macro(4)
22 assert result == [1, 2, 3, 4]
23 assert sys.getrefcount(result) == 3