|
| 1 | +.. toctree:: |
| 2 | + :maxdepth: 3 |
| 3 | + |
| 4 | + |
| 5 | +=========================================== |
| 6 | +A Pythonic Coding Pattern for C Functions |
| 7 | +=========================================== |
| 8 | + |
| 9 | +To avoid all the errors we have seen it is useful to have a C coding pattern for handling ``PyObjects`` that does the following: |
| 10 | + |
| 11 | +* No early returns and a single place for clean up code. |
| 12 | +* Borrowed references incref'd and decref'd correctly. |
| 13 | +* No stale Exception from previous execution path. |
| 14 | +* Exceptions set and tested. |
| 15 | +* NULL is returned when an exception is set. |
| 16 | +* Non-NULL is returned when no exception is set. |
| 17 | + |
| 18 | +The basic pattern in C is similar to Python's try/except/finally pattern: |
| 19 | + |
| 20 | +.. code-block:: c |
| 21 | + |
| 22 | + try: |
| 23 | + /* Do fabulous stuff here. */ |
| 24 | + except: |
| 25 | + /* Handle every abnormal condition and clean up. */ |
| 26 | + finally: |
| 27 | + /* Clean up under normal conditions and return an appropriate value. */ |
| 28 | + |
| 29 | + |
| 30 | +Firstly we set any local ``PyObject`` (s) and the return value to ``NULL``: |
| 31 | + |
| 32 | +.. code-block:: c |
| 33 | + |
| 34 | + static PyObject *function(PyObject *arg_1) { |
| 35 | + PyObject *obj_a = NULL; |
| 36 | + PyObject *ret = NULL; |
| 37 | + |
| 38 | +Then we have a little bit of Pythonic C - this can be omitted: |
| 39 | + |
| 40 | +.. code-block:: c |
| 41 | + |
| 42 | + goto try; /* Pythonic 'C' ;-) */ |
| 43 | + try: |
| 44 | + |
| 45 | +Check that there are no lingering Exceptions: |
| 46 | + |
| 47 | +.. code-block:: c |
| 48 | + |
| 49 | + assert(! PyErr_Occurred()); |
| 50 | + |
| 51 | +An alternative check for no lingering Exceptions: |
| 52 | + |
| 53 | +.. code-block:: c |
| 54 | + |
| 55 | + if(PyErr_Occurred()) { |
| 56 | + goto except; |
| 57 | + } |
| 58 | + |
| 59 | +Now we assume that any argument is a "Borrowed" reference so we increment it (we need a matching ``Py_DECREF`` before function exit, see below). The first pattern assumes a non-NULL argument. |
| 60 | + |
| 61 | +.. code-block:: c |
| 62 | + |
| 63 | + assert(arg_1); |
| 64 | + Py_INCREF(arg_1); |
| 65 | + |
| 66 | +If you are willing to accept NULL arguments then this pattern would be more suitable: |
| 67 | + |
| 68 | +.. code-block:: c |
| 69 | + |
| 70 | + if (arg_1) { |
| 71 | + Py_INCREF(arg_1); |
| 72 | + } |
| 73 | + |
| 74 | +Of course the same test must be used when calling ``Py_DECFREF``, or just use ``Py_XDECREF``. |
| 75 | + |
| 76 | +Now we create any local objects, if they are "Borrowed" references we need to incref them. With any abnormal behaviour we do a local jump straight to the cleanup code. |
| 77 | + |
| 78 | +.. code-block:: c |
| 79 | + |
| 80 | + /* Local object creation. */ |
| 81 | + /* obj_a = ...; */ |
| 82 | + if (! obj_a) { |
| 83 | + PyErr_SetString(PyExc_ValueError, "Ooops."); |
| 84 | + goto except; |
| 85 | + } |
| 86 | + /* If obj_a is a borrowed reference rather than a new reference. */ |
| 87 | + Py_INCREF(obj_a); |
| 88 | + |
| 89 | +Create the return value and deal with abnormal behaviour in the same way: |
| 90 | + |
| 91 | +.. code-block:: c |
| 92 | + |
| 93 | + /* More of your code to do stuff with arg_1 and obj_a. */ |
| 94 | + /* Return object creation, ret should be a new reference otherwise you are in trouble. */ |
| 95 | + /* ret = ...; */ |
| 96 | + if (! ret) { |
| 97 | + PyErr_SetString(PyExc_ValueError, "Ooops again."); |
| 98 | + goto except; |
| 99 | + } |
| 100 | + |
| 101 | +You might want to check the contents of the return value here. On error jump to ``except:`` otherwise jump to ``finally:``. |
| 102 | + |
| 103 | +.. code-block:: c |
| 104 | + |
| 105 | + /* Any return value checking here. */ |
| 106 | + |
| 107 | + /* If success then check exception is clear, |
| 108 | + * then goto finally; with non-NULL return value. */ |
| 109 | + assert(! PyErr_Occurred()); |
| 110 | + assert(ret); |
| 111 | + goto finally; |
| 112 | + |
| 113 | +This is the except block where we cleanup any local objects and set the return value to NULL. |
| 114 | + |
| 115 | +.. code-block:: c |
| 116 | + |
| 117 | + except: |
| 118 | + /* Failure so Py_XDECREF the return value here. */ |
| 119 | + Py_XDECREF(ret); |
| 120 | + /* Check a Python error is set somewhere above. */ |
| 121 | + assert(PyErr_Occurred()); |
| 122 | + /* Signal failure. */ |
| 123 | + ret = NULL; |
| 124 | + |
| 125 | +Notice the ``except:`` block falls through to the ``finally:`` block. |
| 126 | + |
| 127 | +.. code-block:: c |
| 128 | + |
| 129 | + finally: |
| 130 | + /* All _local_ PyObjects declared at the entry point are Py_XDECREF'd here. |
| 131 | + * For new references this will free them. For borrowed references this |
| 132 | + * will return them to their previous refcount. |
| 133 | + */ |
| 134 | + Py_XDECREF(obj_a); |
| 135 | + /* Decrement the ref count of externally supplied the arguments here. |
| 136 | + * If you allow arg_1 == NULL then Py_XDECREF(arg_1). */ |
| 137 | + Py_DECREF(arg_1); |
| 138 | + /* And return...*/ |
| 139 | + return ret; |
| 140 | + } |
| 141 | + |
| 142 | + |
| 143 | +Here is the complete code with minimal comments: |
| 144 | + |
| 145 | +.. code-block:: c |
| 146 | + |
| 147 | + static PyObject *function(PyObject *arg_1) { |
| 148 | + PyObject *obj_a = NULL; |
| 149 | + PyObject *ret = NULL; |
| 150 | + |
| 151 | + goto try; |
| 152 | + try: |
| 153 | + assert(! PyErr_Occurred()); |
| 154 | + assert(arg_1); |
| 155 | + Py_INCREF(arg_1); |
| 156 | + |
| 157 | + /* obj_a = ...; */ |
| 158 | + if (! obj_a) { |
| 159 | + PyErr_SetString(PyExc_ValueError, "Ooops."); |
| 160 | + goto except; |
| 161 | + } |
| 162 | + /* Only do this if obj_a is a borrowed reference. */ |
| 163 | + Py_INCREF(obj_a); |
| 164 | + |
| 165 | + /* More of your code to do stuff with obj_a. */ |
| 166 | + |
| 167 | + /* Return object creation, ret must be a new reference. */ |
| 168 | + /* ret = ...; */ |
| 169 | + if (! ret) { |
| 170 | + PyErr_SetString(PyExc_ValueError, "Ooops again."); |
| 171 | + goto except; |
| 172 | + } |
| 173 | + assert(! PyErr_Occurred()); |
| 174 | + assert(ret); |
| 175 | + goto finally; |
| 176 | + except: |
| 177 | + Py_XDECREF(ret); |
| 178 | + assert(PyErr_Occurred()); |
| 179 | + ret = NULL; |
| 180 | + finally: |
| 181 | + /* Only do this if obj_a is a borrowed reference. */ |
| 182 | + Py_XDECREF(obj_a); |
| 183 | + Py_DECREF(arg_1); |
| 184 | + return ret; |
| 185 | + } |
0 commit comments