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