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 "PythonLimitedAPI.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
30class PyGILRAII {
31 PyGILState_STATE m_GILState;
32
33public:
34 PyGILRAII() : m_GILState(PyGILState_Ensure()) {}
35 ~PyGILRAII() { PyGILState_Release(m_GILState); }
36};
37
38#if (defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x30d00f0)
39
40// Implementation of PyObject_GetOptionalAttr and
41// PyObject_GetOptionalAttrString from
42// https://github.com/python/pythoncapi-compat/, since the function is not part
43// of the limited API.
44
46{
48 if (*result != NULL) {
49 return 1;
50 }
51 if (!PyErr_Occurred()) {
52 return 0;
53 }
56 return 0;
57 }
58 return -1;
59}
60
62{
64 int rc;
66 if (name_obj == NULL) {
67 *result = NULL;
68 return -1;
69 }
72 return rc;
73}
74
75#endif
76
77} // namespace
78
79//- public members -----------------------------------------------------------
81{
82 // Just forward.
83 return GetClass(name, load, kFALSE);
84}
85
86//- public members -----------------------------------------------------------
88{
89 // Class generator to make python classes available to Cling
90
91 if (!load || !name)
92 return 0;
93
94 PyGILRAII thePyGILRAII;
95
96 // first, check whether the name is of a module
97 PyObject *modules = PySys_GetObject(const_cast<char *>("modules"));
101 Py_DecRef(keys);
103
104 if (isModule) {
105 // the normal TClass::GetClass mechanism doesn't allow direct returns, so
106 // do our own check
107 TClass *cl = (TClass *)gROOT->GetListOfClasses()->FindObject(name);
108 if (cl)
109 return cl;
110
111 std::ostringstream nsCode;
112 nsCode << "namespace " << name << " {\n";
113
114 // add all free functions
115 PyObject *bases = PyUnicode_FromString("__bases__");
116 PyObject *mod = PyDict_GetItemString(modules, const_cast<char *>(name));
118 keys = PyDict_Keys(dct);
119
120 for (int i = 0; i < PyList_Size(keys); ++i) {
121 // borrowed references
122 PyObject *key = PyList_GetItem(keys, i);
124
125 // TODO: refactor the code below with the class method code
127 const char *func_name = PyUnicode_AsUTF8AndSize(key, nullptr);
128 if (!func_name) {
129 Py_DecRef(keys);
131 return nullptr; // propagate possible error
132 }
133
134 // figure out number of variables required
135 PyObject *func_code = nullptr;
136 PyObject_GetOptionalAttrString(attr, (char *)"func_code", &func_code);
137 PyObject *var_names = func_code ? PyObject_GetAttrString(func_code, (char *)"co_varnames") : NULL;
138 int nVars = var_names ? PyTuple_Size(var_names) : 0 /* TODO: probably large number, all default? */;
139 if (nVars < 0)
140 nVars = 0;
143
144 nsCode << " TPyReturn " << func_name << "(";
145 for (int ivar = 0; ivar < nVars; ++ivar) {
146 nsCode << "const TPyArg& a" << ivar;
147 if (ivar != nVars - 1)
148 nsCode << ", ";
149 }
150 nsCode << ") {\n";
151 nsCode << " std::vector<TPyArg> v; v.reserve(" << nVars << ");\n";
152
153 // add the variables
154 for (int ivar = 0; ivar < nVars; ++ivar)
155 nsCode << " v.push_back(a" << ivar << ");\n";
156
157 // call dispatch (method or class pointer hard-wired)
158 nsCode << " return TPyReturn(TPyArg::CallMethod((PyObject*)" << std::showbase << (uintptr_t)attr << ", v)); }\n";
159 }
160 }
161
162 Py_DecRef(keys);
164
165 nsCode << " }";
166
167 if (gInterpreter->LoadText(nsCode.str().c_str())) {
168 TClass *klass = new TClass(name, silent);
170 return klass;
171 }
172
173 return nullptr;
174 }
175
176 // determine module and class name part
177 std::string clName = name;
178 std::string::size_type pos = clName.rfind('.');
179
180 if (pos == std::string::npos)
181 return 0; // this isn't a python style class
182
183 std::string mdName = clName.substr(0, pos);
184 clName = clName.substr(pos + 1, std::string::npos);
185
186 // create class in namespace, if it exists (no load, silent)
187 Bool_t useNS = gROOT->GetListOfClasses()->FindObject(mdName.c_str()) != 0;
188 if (!useNS) {
189 // the class itself may exist if we're using the global scope
190 TClass *cl = (TClass *)gROOT->GetListOfClasses()->FindObject(clName.c_str());
191 if (cl)
192 return cl;
193 }
194
195 // locate and get class
196 PyObject *mod = PyImport_AddModule(const_cast<char *>(mdName.c_str()));
197 if (!mod) {
198 PyErr_Clear();
199 return 0; // module apparently disappeared
200 }
201
202 Py_IncRef(mod);
203 PyObject *pyclass = PyDict_GetItemString(PyModule_GetDict(mod), const_cast<char *>(clName.c_str()));
205 Py_DecRef(mod);
206
207 if (!pyclass) {
208 PyErr_Clear(); // the class is no longer available?!
209 return 0;
210 }
211
212 // get a listing of all python-side members
214 if (!attrs) {
215 PyErr_Clear();
217 return 0;
218 }
219
220 // pre-amble Cling proxy class
221 std::ostringstream proxyCode;
222 if (useNS)
223 proxyCode << "namespace " << mdName << " { ";
224 proxyCode << "class " << clName << " {\nprivate:\n PyObject* fPyObject;\npublic:\n";
225
226 // loop over and add member functions
228 for (int i = 0; i < PyList_Size(attrs); ++i) {
229 PyObject *label = PyList_GetItem(attrs, i);
230 Py_IncRef(label);
232
233 // collect only member functions (i.e. callable elements in __dict__)
234 if (PyCallable_Check(attr)) {
235 const char *mtNameCStr = PyUnicode_AsUTF8AndSize(label, nullptr);
236 if(!mtNameCStr) {
237 return nullptr;
238 }
239 std::string mtName = mtNameCStr;
240
241 if (mtName == "__del__") {
243 proxyCode << " ~" << clName << "() { TPyArg::CallDestructor(fPyObject); }\n";
244 continue;
245 }
246
247 Bool_t isConstructor = mtName == "__init__";
248 if (!isConstructor && mtName.find("__", 0, 2) == 0)
249 continue; // skip all other python special funcs
250
251// figure out number of variables required
253 PyObject *var_names = func_code ? PyObject_GetAttrString(func_code, (char *)"co_varnames") : NULL;
254 if (PyErr_Occurred())
255 PyErr_Clear(); // happens for slots; default to 0 arguments
256
257 int nVars =
258 var_names ? PyTuple_Size(var_names) - 1 /* self */ : 0 /* TODO: probably large number, all default? */;
259 if (nVars < 0)
260 nVars = 0;
263
264 // method declaration as appropriate
265 if (isConstructor) {
267 proxyCode << " " << clName << "(";
268 } else // normal method
269 proxyCode << " TPyReturn " << mtName << "(";
270 for (int ivar = 0; ivar < nVars; ++ivar) {
271 proxyCode << "const TPyArg& a" << ivar;
272 if (ivar != nVars - 1)
273 proxyCode << ", ";
274 }
275 proxyCode << ") {\n";
276 proxyCode << " std::vector<TPyArg> v; v.reserve(" << nVars + (isConstructor ? 0 : 1) << ");\n";
277
278 // add the 'self' argument as appropriate
279 if (!isConstructor)
280 proxyCode << " v.push_back(fPyObject);\n";
281
282 // then add the remaining variables
283 for (int ivar = 0; ivar < nVars; ++ivar)
284 proxyCode << " v.push_back(a" << ivar << ");\n";
285
286 // call dispatch (method or class pointer hard-wired)
287 if (!isConstructor)
288 proxyCode << " return TPyReturn(TPyArg::CallMethod((PyObject*)" << std::showbase << (uintptr_t)attr << ", v))";
289 else
290 proxyCode << " TPyArg::CallConstructor(fPyObject, (PyObject*)" << std::showbase << (uintptr_t)pyclass << ", v)";
291 proxyCode << ";\n }\n";
292 }
293
294 // no decref of attr for now (b/c of hard-wired ptr); need cleanup somehow
295 Py_DecRef(label);
296 }
297
298 // special case if no constructor or destructor
299 if (!hasConstructor)
300 proxyCode << " " << clName << "() {\n TPyArg::CallConstructor(fPyObject, (PyObject*)" << std::showbase << (uintptr_t)pyclass
301 << "); }\n";
302
303 if (!hasDestructor)
304 proxyCode << " ~" << clName << "() { TPyArg::CallDestructor(fPyObject); }\n";
305
306 // for now, don't allow copying (ref-counting wouldn't work as expected anyway)
307 proxyCode << " " << clName << "(const " << clName << "&) = delete;\n";
308 proxyCode << " " << clName << "& operator=(const " << clName << "&) = delete;\n";
309
310 // closing and building of Cling proxy class
311 proxyCode << "};";
312 if (useNS)
313 proxyCode << " }";
314
316 // done with pyclass, decref here, assuming module is kept
318
319 // body compilation
320 if (!gInterpreter->LoadText(proxyCode.str().c_str()))
321 return nullptr;
322
323 // done, let ROOT manage the new class
324 TClass *klass = new TClass(useNS ? (mdName + "::" + clName).c_str() : clName.c_str(), silent);
326
327 return klass;
328}
329
330////////////////////////////////////////////////////////////////////////////////
331/// Just forward; based on type name only.
332
334{
335 return GetClass(typeinfo.name(), load, silent);
336}
337
338////////////////////////////////////////////////////////////////////////////////
339/// Just forward; based on type name only
340
342{
343 return GetClass(typeinfo.name(), load);
344}
_object PyObject
constexpr Bool_t kFALSE
Definition RtypesCore.h:108
constexpr Bool_t kTRUE
Definition RtypesCore.h:107
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
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:157
#define gInterpreter
#define gROOT
Definition TROOT.h:414
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
static void AddClass(TClass *cl)
static: Add a class to the list and map of classes.
Definition TClass.cxx:556
virtual TObject * FindObject(const char *name) const
Must be redefined in derived classes.
Definition TObject.cxx:422
TClass * GetClass(const char *name, Bool_t load) override