13. Source Code Layout¶
I find it useful to physically separate out the source code into different categories:
Category |
Language |
|
Testable? |
Where? |
Description |
---|---|---|---|---|---|
Pure Python |
Python |
No |
Yes |
|
Regular Python code tested by pytest or similar. |
CPython interface |
Mostly C |
Yes |
No |
|
C code that defines Python modules and classes. Functions that are exposed directly to Python. |
CPython utilities |
C, C++ |
Yes |
Yes |
|
Utility C/C++ code that works with Python objects but these functions that are not exposed directly to Python. This code can be tested in a C/C++ environment with a specialised test framework. See C++ RAII Wrappers Around PyObject* for some examples. |
C/C++ core |
C, C++ |
No |
Yes |
|
C/C++ code that knows nothing about Python. This code can be tested in a C/C++ environment with a standard C/C++ test framework. |
13.1. Testing CPython Utility Code¶
When making Python C API calls from a C/C++ environment it is important to initialise the Python interpreter. For example, this small program segfaults:
1 2 3 4 5 6 7 8 9 | #include <Python.h>
int main(int /* argc */, const char *[] /* argv[] */) {
/* Forgot this:
Py_Initialize();
*/
PyErr_Format(PyExc_TypeError, "Stuff",);
return 0;
}
|
The reason is that PyErr_Format
calls PyThreadState *thread_state = PyThreadState_Get();
theen thread_state
will be NULL unless the Python interpreter is initialised.
So you need to call Py_Initialize()
to set up statically allocated interpreter data. Alternativley put if (! Py_IsInitialized()) Py_Initialize();
in every test. See: https://docs.python.org/3/c-api/init.html
Here are a couple of useful C++ functions that assert all is well that can be used at the begining of any function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* Returns non zero if Python is initialised and there is no Python error set.
* The second version also checks that the given pointer is non-NULL
* Use this thus, it will do nothing if NDEBUG is defined:
*
* assert(cpython_asserts());
* assert(cpython_asserts(p));
*/
int cpython_asserts() {
return Py_IsInitialized() && PyErr_Occurred() == NULL;
}
int cpython_asserts(PyObject *pobj) {
return cpython_asserts() && pobj != NULL;
}
|