@@ -114,7 +114,8 @@ This new reference wrapper can be used as follows:
114
114
.. _cpp_and_cpython.handling_default_arguments :
115
115
116
116
.. index ::
117
- single: C++; Default Mutable Arguments
117
+ single: Parsing Arguments Example; Default Mutable Arguments
118
+ single: Default Mutable Arguments; C++
118
119
119
120
============================================
120
121
Handling Default Arguments
@@ -123,49 +124,261 @@ Handling Default Arguments
123
124
Handling default, possibly mutable, arguments in a pythonic way is described here:
124
125
:ref: `cpython_default_mutable_arguments `.
125
126
It is quite complicated to get it right but C++ can ease the pain with a generic class to simplify handling default
126
- arguments in CPython functions:
127
+ arguments in CPython functions.
128
+
129
+ The actual code is in ``src/cpy/ParseArgs/cParseArgsHelper.cpp `` but here it is, simplified to its essentials:
127
130
128
131
.. code-block :: cpp
129
132
130
133
class DefaultArg {
131
134
public:
132
- DefaultArg(PyObject *new_ref) : m_arg { NULL }, m_default { new_ref } {}
133
- // Allow setting of the (optional) argument with PyArg_ParseTupleAndKeywords
134
- PyObject **operator&() { m_arg = NULL; return &m_arg; }
135
- // Access the argument or the default if default.
136
- operator PyObject*() const { return m_arg ? m_arg : m_default; }
137
- // Test if constructed successfully from the new reference.
135
+ DefaultArg(PyObject *new_ref) : m_arg(NULL), m_default(new_ref) {}
136
+ /// Allow setting of the (optional) argument with
137
+ /// PyArg_ParseTupleAndKeywords
138
+ PyObject **operator&() {
139
+ m_arg = NULL;
140
+ return &m_arg;
141
+ }
142
+ /// Access the argument or the default if default.
143
+ operator PyObject *() const {
144
+ return m_arg ? m_arg : m_default;
145
+ }
146
+ PyObject *obj() const {
147
+ return m_arg ? m_arg : m_default;
148
+ }
149
+ /// Test if constructed successfully from the new reference.
138
150
explicit operator bool() { return m_default != NULL; }
139
151
protected:
140
152
PyObject *m_arg;
141
153
PyObject *m_default;
142
154
};
143
155
144
- Suppose we have the Python function signature of ``def function(encoding='utf8', cache={}): `` then in C/C++ we can do this:
156
+ ---------------------------
157
+ Immutable Default Arguments
158
+ ---------------------------
159
+
160
+ Suppose we have the Python function equivalent to the Python function:
161
+
162
+ .. code-block :: python
163
+
164
+ def parse_defaults_with_helper_class (
165
+ encoding_m : str = " utf-8" ,
166
+ the_id_m : int = 1024 ,
167
+ log_interval_m : float = 8.0 ):
168
+ return encoding_m, the_id_m, log_interval_m
169
+
170
+ Here it is in C:
145
171
146
172
.. code-block :: cpp
147
173
148
- PyObject *
149
- function(PyObject * /* module */, PyObject *args, PyObject *kwargs) {
150
- /* ... */
151
- static DefaultArg encoding(PyUnicode_FromString("utf8"));
152
- static DefaultArg cache(PyDict_New());
153
- /* Check constructed OK. */
154
- if (! encoding || ! cache) {
174
+ static PyObject *
175
+ parse_defaults_with_helper_class(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwds) {
176
+ PyObject *ret = NULL;
177
+ /* Initialise default arguments. */
178
+ static DefaultArg encoding_c(PyUnicode_FromString("utf-8"));
179
+ static DefaultArg the_id_c(PyLong_FromLong(DEFAULT_ID));
180
+ static DefaultArg log_interval_c(PyFloat_FromDouble(DEFAULT_FLOAT));
181
+
182
+ /* Check that the defaults are non-NULL i.e. succesful. */
183
+ if (!encoding_c || !the_id_c || !log_interval_c) {
155
184
return NULL;
156
185
}
157
- static const char *kwlist[] = { "encoding", "cache", NULL };
158
- if (! PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", const_cast<char**>(kwlist), &encoding, &cache)) {
186
+
187
+ static const char *kwlist[] = {"encoding", "the_id", "log_interval", NULL};
188
+ /* &encoding etc. accesses &m_arg in DefaultArg because of PyObject **operator&() */
189
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO",
190
+ const_cast<char **>(kwlist),
191
+ &encoding_c, &the_id_c, &log_interval_c)) {
159
192
return NULL;
160
193
}
161
- /* Then just use encoding, cache as if they were a PyObject* (possibly
162
- * might need to be cast to some specific PyObject*). */
163
-
194
+
195
+ PY_DEFAULT_CHECK(encoding_c, PyUnicode_Check, "str");
196
+ PY_DEFAULT_CHECK(the_id_c, PyLong_Check, "int");
197
+ PY_DEFAULT_CHECK(log_interval_c, PyFloat_Check, "float");
198
+
199
+ /*
200
+ * Use encoding, the_id, must_log from here on as PyObject* since we have
201
+ * operator PyObject*() const ...
202
+ *
203
+ * So if we have a function:
204
+ * set_encoding(PyObject *obj) { ... }
205
+ */
206
+ // set_encoding(encoding);
164
207
/* ... */
208
+
209
+ /* Py_BuildValue("O") increments the reference count. */
210
+ ret = Py_BuildValue("OOO", encoding_c.obj(), the_id_c.obj(), log_interval_c.obj());
211
+ return ret;
165
212
}
166
213
167
214
The full code is in ``src/cpy/cParseArgsHelper.cpp `` and the tests in ``tests/unit/test_c_parse_args_helper.py ``.
168
215
216
+ Here is an example test:
217
+
218
+ .. code-block :: python
219
+
220
+ @pytest.mark.parametrize (
221
+ ' args, expected' ,
222
+ (
223
+ (
224
+ (),
225
+ (' utf-8' , 1024 , 8.0 ),
226
+ ),
227
+ (
228
+ (' Encoding' , 4219 , 16.0 ),
229
+ (' Encoding' , 4219 , 16.0 ),
230
+ ),
231
+ ),
232
+ )
233
+ def test_parse_defaults_with_helper_class (args , expected ):
234
+ assert cParseArgsHelper.parse_defaults_with_helper_class(* args) == expected
235
+
236
+ -------------------------
237
+ Mutable Default Arguments
238
+ -------------------------
239
+
240
+ The same class can be used for mutable arguments.
241
+ The following emulates this Python function:
242
+
243
+ .. code-block :: python
244
+
245
+ def parse_mutable_defaults_with_helper_class (obj , default_list = []):
246
+ default_list.append(obj)
247
+ return default_list
248
+
249
+ Here it is in C:
250
+
251
+ .. code-block :: c
252
+
253
+ /** Parse the args where we are simulating mutable default of an empty list.
254
+ * This uses the helper class.
255
+ *
256
+ * This is equivalent to:
257
+ *
258
+ * def parse_mutable_defaults_with_helper_class(obj, default_list=[]):
259
+ * default_list.append(obj)
260
+ * return default_list
261
+ *
262
+ * This adds the object to the list and returns None.
263
+ *
264
+ * This imitates the Python way of handling defaults.
265
+ */
266
+ static PyObject *parse_mutable_defaults_with_helper_class(PyObject *Py_UNUSED(module),
267
+ PyObject *args) {
268
+ PyObject *ret = NULL;
269
+ /* Pointers to the non-default argument, initialised by PyArg_ParseTuple below. */
270
+ PyObject *arg_0 = NULL;
271
+ static DefaultArg list_argument_c(PyList_New(0));
272
+
273
+ if (!PyArg_ParseTuple(args, "O|O", &arg_0, &list_argument_c)) {
274
+ goto except;
275
+ }
276
+ PY_DEFAULT_CHECK(list_argument_c, PyList_Check, "list");
277
+
278
+ /* Your code here...*/
279
+
280
+ /* Append the first argument to the second.
281
+ * PyList_Append() increments the refcount of arg_0. */
282
+ if (PyList_Append(list_argument_c, arg_0)) {
283
+ PyErr_SetString(PyExc_RuntimeError, "Can not append to list!");
284
+ goto except;
285
+ }
286
+
287
+ /* Success. */
288
+ assert(!PyErr_Occurred());
289
+ /* This increments the default or the given argument. */
290
+ Py_INCREF(list_argument_c);
291
+ ret = list_argument_c;
292
+ goto finally;
293
+ except:
294
+ assert(PyErr_Occurred());
295
+ Py_XDECREF(ret);
296
+ ret = NULL;
297
+ finally:
298
+ return ret;
299
+ }
300
+
301
+ The code is in ``src/cpy/ParseArgs/cParseArgsHelper.cpp ``.
302
+
303
+ Here are some tests from ``tests/unit/test_c_parse_args_helper.py ``.
304
+ Firstly establish the known Python behaviour:
305
+
306
+ .. code-block :: python
307
+
308
+ def test_parse_mutable_defaults_with_helper_class_python ():
309
+ """ A local Python equivalent of cParseArgsHelper.parse_mutable_defaults_with_helper_class()."""
310
+
311
+ def parse_mutable_defaults_with_helper_class (obj , default_list = []):
312
+ default_list.append(obj)
313
+ return default_list
314
+
315
+ result = parse_mutable_defaults_with_helper_class(1 )
316
+ assert sys.getrefcount(result) == 3
317
+ assert result == [1 , ]
318
+ result = parse_mutable_defaults_with_helper_class(2 )
319
+ assert sys.getrefcount(result) == 3
320
+ assert result == [1 , 2 ]
321
+ result = parse_mutable_defaults_with_helper_class(3 )
322
+ assert sys.getrefcount(result) == 3
323
+ assert result == [1 , 2 , 3 ]
324
+
325
+ local_list = []
326
+ assert sys.getrefcount(local_list) == 2
327
+ assert parse_mutable_defaults_with_helper_class(10 , local_list) == [10 ]
328
+ assert sys.getrefcount(local_list) == 2
329
+ assert parse_mutable_defaults_with_helper_class(11 , local_list) == [10 , 11 ]
330
+ assert sys.getrefcount(local_list) == 2
331
+
332
+ result = parse_mutable_defaults_with_helper_class(4 )
333
+ assert result == [1 , 2 , 3 , 4 ]
334
+ assert sys.getrefcount(result) == 3
335
+
336
+ And now the equivalent in C:
337
+
338
+ .. code-block :: python
339
+
340
+ from cPyExtPatt import cParseArgsHelper
341
+
342
+ def test_parse_mutable_defaults_with_helper_class_c ():
343
+ result = cParseArgsHelper.parse_mutable_defaults_with_helper_class(1 )
344
+ assert sys.getrefcount(result) == 3
345
+ assert result == [1 , ]
346
+ result = cParseArgsHelper.parse_mutable_defaults_with_helper_class(2 )
347
+ assert sys.getrefcount(result) == 3
348
+ assert result == [1 , 2 ]
349
+ result = cParseArgsHelper.parse_mutable_defaults_with_helper_class(3 )
350
+ assert sys.getrefcount(result) == 3
351
+ assert result == [1 , 2 , 3 ]
352
+
353
+ local_list = []
354
+ assert sys.getrefcount(local_list) == 2
355
+ assert cParseArgsHelper.parse_mutable_defaults_with_helper_class(10 , local_list) == [10 ]
356
+ assert sys.getrefcount(local_list) == 2
357
+ assert cParseArgsHelper.parse_mutable_defaults_with_helper_class(11 , local_list) == [10 , 11 ]
358
+ assert sys.getrefcount(local_list) == 2
359
+
360
+ result = cParseArgsHelper.parse_mutable_defaults_with_helper_class(4 )
361
+ assert result == [1 , 2 , 3 , 4 ]
362
+ assert sys.getrefcount(result) == 3
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
169
382
.. index ::
170
383
single: C++; Homogeneous Containers
171
384
single: C++; Project PyCppContainers
0 commit comments