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