-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathpython.hpp
145 lines (134 loc) · 6.75 KB
/
python.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#pragma once
#include <string>
// ssize_t is non-standard, for MSVC we need to define it
#if defined(_MSC_VER)
#include <BaseTsd.h>
using ssize_t = SSIZE_T;
#endif
namespace pythonx::python {
// We only use the Limited C API, which stays compatible across Python
// minor versions. At the moment we assume Python 3.10+, the list of
// available functions can be found in [1].
//
// Other than the C API functions, we also use Python standard library
// functions via PyObject_Call. This is fine as long as the functions
// are present in the lowest supported Python version and seem stable
// (in particular, they are not deprecated). Keep in mind that the
// C API should be preferred when possible, since invoking lower level
// operations directly should be more efficient in most cases.
//
// Note that we manage the library manually with `dlopen`, instead of
// linking at compile time and loading together with the NIF lib. This
// makes the development slightly more annoying, since we need to
// declare and load the individual symbols ourselves, however it makes
// the overall setup more flexible:
//
// 1. We want the Python library location to be configured by the
// user. We could do that by: (a) making sure the NIF library
// depends on a relative libpython.so/python.dll; (b) at runtime,
// symlink or copy the configured Python library at that relative
// location; (c) load the NIF only after symlink is in place,
// rather than upfront. Step (a) requires patching the .so/.dll
// differently on each platform; on Windows this requires compiling
// and attaching and extra manifest to the .dll. With `dlopen` we
// can simply use the configured path.
//
// 2. It prevents from accidentally using symbols out of Limited API.
// Technically setting Py_LIMITED_API hides a subset of function
// declarations, but there are at least certain macros that stay
// visible. By adding the symbols manually, we can make sure they
// are indeed part of the Limited API.
//
// 3. If necessary, we could conditionally use functions added to
// Limited API in later versions and have fallback implementations.
//
// 4. Compiling the NIF does not require Python at all, which makes
// it simpler. Though the main tradeoff is not being able to use
// the Python library headers.
//
// 5. After uninitializing Python, we should be able to unload the
// library, though there may not be an actual use case to make
// this relevant. It is also worth noting that, while in principle
// it should be possible to reinitialize Python, it can lead to
// issues in practice. For example, doing so while using numpy
// simply does not work, see [2] for discussion points.
//
// [1]: https://docs.python.org/3.10/c-api/stable.html#stable-abi-list
// [2]: https://bugs.python.org/issue34309
// Opaque types
using PyInterpreterStatePtr = void *;
using PyObjectPtr = void *;
using PyThreadStatePtr = void *;
using Py_ssize_t = ssize_t;
// Functions
extern PyObjectPtr (*PyBool_FromLong)(long int);
extern int (*PyBytes_AsStringAndSize)(PyObjectPtr, char **, Py_ssize_t *);
extern PyObjectPtr (*PyBytes_FromStringAndSize)(const char *, Py_ssize_t);
extern PyObjectPtr (*PyDict_Copy)(PyObjectPtr);
extern PyObjectPtr (*PyDict_GetItem)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyDict_GetItemString)(PyObjectPtr, const char *);
extern PyObjectPtr (*PyDict_New)();
extern int (*PyDict_Next)(PyObjectPtr, Py_ssize_t *, PyObjectPtr *,
PyObjectPtr *);
extern int (*PyDict_SetItem)(PyObjectPtr, PyObjectPtr, PyObjectPtr);
extern int (*PyDict_SetItemString)(PyObjectPtr, const char *, PyObjectPtr);
extern Py_ssize_t (*PyDict_Size)(PyObjectPtr);
extern void (*PyErr_Fetch)(PyObjectPtr *, PyObjectPtr *, PyObjectPtr *);
extern PyObjectPtr (*PyErr_Occurred)();
extern PyObjectPtr (*PyEval_GetBuiltins)();
extern PyObjectPtr (*PyEval_EvalCode)(PyObjectPtr, PyObjectPtr, PyObjectPtr);
extern void (*PyEval_RestoreThread)(PyThreadStatePtr);
extern PyThreadStatePtr (*PyEval_SaveThread)();
extern double (*PyFloat_AsDouble)(PyObjectPtr);
extern PyObjectPtr (*PyFloat_FromDouble)(double);
extern PyObjectPtr (*PyImport_AddModule)(const char *);
extern PyObjectPtr (*PyImport_ImportModule)(const char *);
extern PyInterpreterStatePtr (*PyInterpreterState_Get)();
extern PyObjectPtr (*PyIter_Next)(PyObjectPtr);
extern int (*PyList_Append)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyList_GetItem)(PyObjectPtr, Py_ssize_t);
extern PyObjectPtr (*PyList_New)(Py_ssize_t);
extern Py_ssize_t (*PyList_Size)(PyObjectPtr);
extern int (*PyList_SetItem)(PyObjectPtr, Py_ssize_t, PyObjectPtr);
extern long long (*PyLong_AsLongLongAndOverflow)(PyObjectPtr, int *);
extern PyObjectPtr (*PyLong_FromLongLong)(long long);
extern PyObjectPtr (*PyLong_FromString)(const char *, char **, int);
extern PyObjectPtr (*PyLong_FromUnsignedLongLong)(unsigned long long);
extern PyObjectPtr (*PyModule_GetDict)(PyObjectPtr);
extern PyObjectPtr (*PyObject_Call)(PyObjectPtr, PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyObject_CallNoArgs)(PyObjectPtr);
extern PyObjectPtr (*PyObject_GetAttrString)(PyObjectPtr, const char *);
extern PyObjectPtr (*PyObject_GetIter)(PyObjectPtr);
extern int (*PyObject_IsInstance)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyObject_Repr)(PyObjectPtr);
extern int (*PyObject_SetAttrString)(PyObjectPtr, const char *, PyObjectPtr);
extern PyObjectPtr (*PyObject_Str)(PyObjectPtr);
extern int (*PySet_Add)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PySet_New)(PyObjectPtr);
extern Py_ssize_t (*PySet_Size)(PyObjectPtr);
extern PyThreadStatePtr (*PyThreadState_New)(PyInterpreterStatePtr);
extern PyObjectPtr (*PyTuple_GetItem)(PyObjectPtr, Py_ssize_t);
extern PyObjectPtr (*PyTuple_New)(Py_ssize_t);
extern PyObjectPtr (*PyTuple_Pack)(Py_ssize_t, ...);
extern int (*PyTuple_SetItem)(PyObjectPtr, Py_ssize_t, PyObjectPtr);
extern Py_ssize_t (*PyTuple_Size)(PyObjectPtr);
extern const char *(*PyUnicode_AsUTF8AndSize)(PyObjectPtr, Py_ssize_t *);
extern PyObjectPtr (*PyUnicode_FromStringAndSize)(const char *, Py_ssize_t);
extern PyObjectPtr (*Py_BuildValue)(const char *, ...);
extern PyObjectPtr (*Py_CompileString)(const char *, const char *, int);
extern void (*Py_DecRef)(PyObjectPtr);
extern void (*Py_IncRef)(PyObjectPtr);
extern void (*Py_InitializeEx)(int);
extern int (*Py_IsFalse)(PyObjectPtr);
extern int (*Py_IsNone)(PyObjectPtr);
extern int (*Py_IsTrue)(PyObjectPtr);
extern void (*Py_SetPythonHome)(const wchar_t *);
extern void (*Py_SetProgramName)(const wchar_t *);
// Opens Python dynamic library at the given path and looks up all
// relevant symbols.
//
// Raises `std::runtime_error` if loading fails.
void load_python_library(std::string path);
// Closes the Python library.
void unload_python_library();
} // namespace pythonx::python