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
31 namespace CPyCppyy {
33  namespace PyStrings {
35  }
36 }
37 
38 namespace {
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 
306 TClass *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 
314 TClass *TPyClassGenerator::GetClass(const std::type_info &typeinfo, Bool_t load)
315 {
316  return GetClass(typeinfo.name(), load);
317 }
CPyCppyy
Set of helper functions that are invoked from the pythonizors, on the Python side.
Definition: TPyClassGenerator.cxx:31
CPyCppyy::gDictLookupActive
R__EXTERN bool gDictLookupActive
Definition: TPyClassGenerator.cxx:32
kTRUE
const Bool_t kTRUE
Definition: RtypesCore.h:91
PyObject
_object PyObject
Definition: PyMethodBase.h:42
pyname
#define pyname
Definition: TMCParticle.cxx:19
gInterpreter
#define gInterpreter
Definition: TInterpreter.h:560
TClass::AddClass
static void AddClass(TClass *cl)
static: Add a class to the list and map of classes.
Definition: TClass.cxx:479
TClass.h
TList.h
CPyCppyy_PyText_FromString
#define CPyCppyy_PyText_FromString
Definition: CPyCppyy.h:102
bool
TROOT.h
TPyClassGenerator::GetClass
virtual TClass * GetClass(const char *name, Bool_t load)
Definition: TPyClassGenerator.cxx:48
CPyCppyy::PyStrings::gBases
R__EXTERN PyObject * gBases
Definition: TPyClassGenerator.cxx:34
kFALSE
const Bool_t kFALSE
Definition: RtypesCore.h:92
CPyCppyy.h
CPyCppyy_PyText_AsString
#define CPyCppyy_PyText_AsString
Definition: CPyCppyy.h:97
TInterpreter.h
TClass
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition: TClass.h:80
name
char name[80]
Definition: TGX11.cxx:110
R__EXTERN
#define R__EXTERN
Definition: DllImport.h:27
TPyReturn.h
gROOT
#define gROOT
Definition: TROOT.h:406