Logo ROOT  
Reference Guide
TPyClassGenerator.cxx
Go to the documentation of this file.
1// Author: Enric Tejedor CERN 08/2019
2// Original PyROOT code by Wim Lavrijsen, LBL
3//
4// /*************************************************************************
5// * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
6// * All rights reserved. *
7// * *
8// * For the licensing terms see $ROOTSYS/LICENSE. *
9// * For the list of contributors see $ROOTSYS/README/CREDITS. *
10// *************************************************************************/
11
12// Bindings
13// CPyCppyy.h must be go first, since it includes Python.h, which must be
14// included before any standard header
15#include "CPyCppyy.h"
16#include "TPyClassGenerator.h"
17#include "TPyReturn.h"
18
19// ROOT
20#include "TClass.h"
21#include "TInterpreter.h"
22#include "TROOT.h"
23#include "TList.h"
24
25// Standard
26#include <sstream>
27#include <string>
28#include <typeinfo>
29
30// needed to properly resolve (dllimport) symbols on Windows
31namespace CPyCppyy {
33 namespace PyStrings {
35 }
36}
37
38namespace {
39 class PyGILRAII {
40 PyGILState_STATE m_GILState;
41 public:
42 PyGILRAII() : m_GILState(PyGILState_Ensure()) { }
43 ~PyGILRAII() { PyGILState_Release(m_GILState); }
44 };
45}
46
47//- public members -----------------------------------------------------------
49{
50 // Just forward.
51 return GetClass(name, load, kFALSE);
52}
53
54//- public members -----------------------------------------------------------
56{
57 // Class generator to make python classes available to Cling
58
59 // called if all other class generators failed, attempt to build from python class
61 return 0; // call originated from python
62
63 if (!load || !name)
64 return 0;
65
66 PyGILRAII thePyGILRAII;
67
68 // first, check whether the name is of a module
69 PyObject *modules = PySys_GetObject(const_cast<char *>("modules"));
71 PyObject *keys = PyDict_Keys(modules);
72 Bool_t isModule = PySequence_Contains(keys, pyname);
73 Py_DECREF(keys);
74 Py_DECREF(pyname);
75
76 if (isModule) {
77 // the normal TClass::GetClass mechanism doesn't allow direct returns, so
78 // do our own check
79 TClass *cl = (TClass *)gROOT->GetListOfClasses()->FindObject(name);
80 if (cl)
81 return cl;
82
83 std::ostringstream nsCode;
84 nsCode << "namespace " << name << " {\n";
85
86 // add all free functions
87 PyObject *mod = PyDict_GetItemString(modules, const_cast<char *>(name));
88 PyObject *dct = PyModule_GetDict(mod);
89 keys = PyDict_Keys(dct);
90
91 for (int i = 0; i < PyList_GET_SIZE(keys); ++i) {
92 PyObject *key = PyList_GET_ITEM(keys, i);
93 Py_INCREF(key);
94
95 PyObject *attr = PyDict_GetItem(dct, key);
96 Py_INCREF(attr);
97
98 // TODO: refactor the code below with the class method code
99 if (PyCallable_Check(attr) && !(PyClass_Check(attr) || PyObject_HasAttr(attr, CPyCppyy::PyStrings::gBases))) {
100 std::string func_name = CPyCppyy_PyText_AsString(key);
101
102 // figure out number of variables required
103 PyObject *func_code = PyObject_GetAttrString(attr, (char *)"func_code");
104 PyObject *var_names = func_code ? PyObject_GetAttrString(func_code, (char *)"co_varnames") : NULL;
105 int nVars = var_names ? PyTuple_GET_SIZE(var_names) : 0 /* TODO: probably large number, all default? */;
106 if (nVars < 0)
107 nVars = 0;
108 Py_XDECREF(var_names);
109 Py_XDECREF(func_code);
110
111 nsCode << " TPyReturn " << func_name << "(";
112 for (int ivar = 0; ivar < nVars; ++ivar) {
113 nsCode << "const TPyArg& a" << ivar;
114 if (ivar != nVars - 1)
115 nsCode << ", ";
116 }
117 nsCode << ") {\n";
118 nsCode << " std::vector<TPyArg> v; v.reserve(" << nVars << ");\n";
119
120 // add the variables
121 for (int ivar = 0; ivar < nVars; ++ivar)
122 nsCode << " v.push_back(a" << ivar << ");\n";
123
124 // call dispatch (method or class pointer hard-wired)
125 nsCode << " return TPyReturn(TPyArg::CallMethod((PyObject*)" << std::showbase << (uintptr_t)attr << ", v)); }\n";
126 }
127
128 Py_DECREF(attr);
129 Py_DECREF(key);
130 }
131
132 Py_DECREF(keys);
133
134 nsCode << " }";
135
136 if (gInterpreter->LoadText(nsCode.str().c_str())) {
137 TClass *klass = new TClass(name, silent);
138 TClass::AddClass(klass);
139 return klass;
140 }
141
142 return nullptr;
143 }
144
145 // determine module and class name part
146 std::string clName = name;
147 std::string::size_type pos = clName.rfind('.');
148
149 if (pos == std::string::npos)
150 return 0; // this isn't a python style class
151
152 std::string mdName = clName.substr(0, pos);
153 clName = clName.substr(pos + 1, std::string::npos);
154
155 // create class in namespace, if it exists (no load, silent)
156 Bool_t useNS = gROOT->GetListOfClasses()->FindObject(mdName.c_str()) != 0;
157 if (!useNS) {
158 // the class itself may exist if we're using the global scope
159 TClass *cl = (TClass *)gROOT->GetListOfClasses()->FindObject(clName.c_str());
160 if (cl)
161 return cl;
162 }
163
164 // locate and get class
165 PyObject *mod = PyImport_AddModule(const_cast<char *>(mdName.c_str()));
166 if (!mod) {
167 PyErr_Clear();
168 return 0; // module apparently disappeared
169 }
170
171 Py_INCREF(mod);
172 PyObject *pyclass = PyDict_GetItemString(PyModule_GetDict(mod), const_cast<char *>(clName.c_str()));
173 Py_XINCREF(pyclass);
174 Py_DECREF(mod);
175
176 if (!pyclass) {
177 PyErr_Clear(); // the class is no longer available?!
178 return 0;
179 }
180
181 // get a listing of all python-side members
182 PyObject *attrs = PyObject_Dir(pyclass);
183 if (!attrs) {
184 PyErr_Clear();
185 Py_DECREF(pyclass);
186 return 0;
187 }
188
189 // pre-amble Cling proxy class
190 std::ostringstream proxyCode;
191 if (useNS)
192 proxyCode << "namespace " << mdName << " { ";
193 proxyCode << "class " << clName << " {\nprivate:\n PyObject* fPyObject;\npublic:\n";
194
195 // loop over and add member functions
196 Bool_t hasConstructor = kFALSE, hasDestructor = kFALSE;
197 for (int i = 0; i < PyList_GET_SIZE(attrs); ++i) {
198 PyObject *label = PyList_GET_ITEM(attrs, i);
199 Py_INCREF(label);
200 PyObject *attr = PyObject_GetAttr(pyclass, label);
201
202 // collect only member functions (i.e. callable elements in __dict__)
203 if (PyCallable_Check(attr)) {
204 std::string mtName = CPyCppyy_PyText_AsString(label);
205
206 if (mtName == "__del__") {
207 hasDestructor = kTRUE;
208 proxyCode << " ~" << clName << "() { TPyArg::CallDestructor(fPyObject); }\n";
209 continue;
210 }
211
212 Bool_t isConstructor = mtName == "__init__";
213 if (!isConstructor && mtName.find("__", 0, 2) == 0)
214 continue; // skip all other python special funcs
215
216// figure out number of variables required
217#if PY_VERSION_HEX < 0x03000000
218 PyObject *im_func = PyObject_GetAttrString(attr, (char *)"im_func");
219 PyObject *func_code = im_func ? PyObject_GetAttrString(im_func, (char *)"func_code") : NULL;
220#else
221 PyObject *func_code = PyObject_GetAttrString(attr, "__code__");
222#endif
223 PyObject *var_names = func_code ? PyObject_GetAttrString(func_code, (char *)"co_varnames") : NULL;
224 if (PyErr_Occurred())
225 PyErr_Clear(); // happens for slots; default to 0 arguments
226
227 int nVars =
228 var_names ? PyTuple_GET_SIZE(var_names) - 1 /* self */ : 0 /* TODO: probably large number, all default? */;
229 if (nVars < 0)
230 nVars = 0;
231 Py_XDECREF(var_names);
232 Py_XDECREF(func_code);
233#if PY_VERSION_HEX < 0x03000000
234 Py_XDECREF(im_func);
235#endif
236
237 // method declaration as appropriate
238 if (isConstructor) {
239 hasConstructor = kTRUE;
240 proxyCode << " " << clName << "(";
241 } else // normal method
242 proxyCode << " TPyReturn " << mtName << "(";
243 for (int ivar = 0; ivar < nVars; ++ivar) {
244 proxyCode << "const TPyArg& a" << ivar;
245 if (ivar != nVars - 1)
246 proxyCode << ", ";
247 }
248 proxyCode << ") {\n";
249 proxyCode << " std::vector<TPyArg> v; v.reserve(" << nVars + (isConstructor ? 0 : 1) << ");\n";
250
251 // add the 'self' argument as appropriate
252 if (!isConstructor)
253 proxyCode << " v.push_back(fPyObject);\n";
254
255 // then add the remaining variables
256 for (int ivar = 0; ivar < nVars; ++ivar)
257 proxyCode << " v.push_back(a" << ivar << ");\n";
258
259 // call dispatch (method or class pointer hard-wired)
260 if (!isConstructor)
261 proxyCode << " return TPyReturn(TPyArg::CallMethod((PyObject*)" << std::showbase << (uintptr_t)attr << ", v))";
262 else
263 proxyCode << " TPyArg::CallConstructor(fPyObject, (PyObject*)" << std::showbase << (uintptr_t)pyclass << ", v)";
264 proxyCode << ";\n }\n";
265 }
266
267 // no decref of attr for now (b/c of hard-wired ptr); need cleanup somehow
268 Py_DECREF(label);
269 }
270
271 // special case if no constructor or destructor
272 if (!hasConstructor)
273 proxyCode << " " << clName << "() {\n TPyArg::CallConstructor(fPyObject, (PyObject*)" << std::showbase << (uintptr_t)pyclass
274 << "); }\n";
275
276 if (!hasDestructor)
277 proxyCode << " ~" << clName << "() { TPyArg::CallDestructor(fPyObject); }\n";
278
279 // for now, don't allow copying (ref-counting wouldn't work as expected anyway)
280 proxyCode << " " << clName << "(const " << clName << "&) = delete;\n";
281 proxyCode << " " << clName << "& operator=(const " << clName << "&) = delete;\n";
282
283 // closing and building of Cling proxy class
284 proxyCode << "};";
285 if (useNS)
286 proxyCode << " }";
287
288 Py_DECREF(attrs);
289 // done with pyclass, decref here, assuming module is kept
290 Py_DECREF(pyclass);
291
292 // body compilation
293 if (!gInterpreter->LoadText(proxyCode.str().c_str()))
294 return nullptr;
295
296 // done, let ROOT manage the new class
297 TClass *klass = new TClass(useNS ? (mdName + "::" + clName).c_str() : clName.c_str(), silent);
298 TClass::AddClass(klass);
299
300 return klass;
301}
302
303////////////////////////////////////////////////////////////////////////////////
304/// Just forward; based on type name only.
305
306TClass *TPyClassGenerator::GetClass(const std::type_info &typeinfo, Bool_t load, Bool_t silent)
307{
308 return GetClass(typeinfo.name(), load, silent);
309}
310
311////////////////////////////////////////////////////////////////////////////////
312/// Just forward; based on type name only
313
314TClass *TPyClassGenerator::GetClass(const std::type_info &typeinfo, Bool_t load)
315{
316 return GetClass(typeinfo.name(), load);
317}
#define CPyCppyy_PyText_AsString
Definition: CPyCppyy.h:97
#define CPyCppyy_PyText_FromString
Definition: CPyCppyy.h:102
#define R__EXTERN
Definition: DllImport.h:27
_object PyObject
Definition: PyMethodBase.h:42
const Bool_t kFALSE
Definition: RtypesCore.h:101
bool Bool_t
Definition: RtypesCore.h:63
const Bool_t kTRUE
Definition: RtypesCore.h:100
char name[80]
Definition: TGX11.cxx:110
#define gInterpreter
Definition: TInterpreter.h:562
#define pyname
Definition: TMCParticle.cxx:19
#define gROOT
Definition: TROOT.h:404
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition: TClass.h:80
static void AddClass(TClass *cl)
static: Add a class to the list and map of classes.
Definition: TClass.cxx:494
virtual TClass * GetClass(const char *name, Bool_t load)
R__EXTERN PyObject * gBases
Set of helper functions that are invoked from the pythonizors, on the Python side.
R__EXTERN bool gDictLookupActive