ATLAS Offline Software
Loading...
Searching...
No Matches
PyROOTInspector.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
5
6//#define PYROOT_INSPECTOR_DBG 1
7#define PYROOT_INSPECTOR_DBG 0
8
9// Interacts with python, so only excuted single-threaded (GIL).
12
13
14#include "Python.h"
15
16// ROOT includes
17#include "TClassRef.h"
18#include "TDataMember.h"
19#include "TDataType.h"
20#include "TVirtualCollectionProxy.h"
21#include "TClassEdit.h"
22
23#include "Utility.h"
24
25// PyROOT includes
26#include <TPython.h>
27#include "CPyCppyy/API.h"
28
29// fixes 'dereferencing type-punned pointer will break strict-aliasing rules'
30#ifdef Py_True
31#undef Py_True
32#define Py_True ( (PyObject*)(void*)&_Py_TrueStruct )
33#endif
34#ifdef Py_False
35#undef Py_False
36#define Py_False ( (PyObject*)(void*)&_Py_ZeroStruct )
37#endif
38
39
40// ------------------------------------
42
43#include "TClass.h"
44#include "TPython.h"
45#include "TList.h"
46
47#include <iostream>
48
49namespace {
51to_pyobj(void* ptr, EDataType mbr_dtype)
52{
53 switch(mbr_dtype) {
54
55 case kChar_t:
56 case kUChar_t:
57 case kchar:
58 return Py_BuildValue("c", ((char*)ptr)[0] & 0x7F); // WTF ?!?!
59
60 case kBool_t:
61 return Py_BuildValue("i", (*(bool*)ptr) ? 1 : 0);
62
63 case kShort_t:
64 return Py_BuildValue("h", (*(short*)ptr));
65
66 case kUShort_t:
67 return Py_BuildValue("H", (*(unsigned short*)ptr));
68
69 case kCounter:
70 case kInt_t:
71 return Py_BuildValue("i", (*(int*)ptr));
72
73 case kUInt_t:
74 return Py_BuildValue("I", (*(unsigned int*)ptr));
75
76 case kDouble_t:
77 case kDouble32_t:
78 return Py_BuildValue("d", (*(double*)ptr));
79
80 case kFloat_t:
81 case kFloat16_t:
82 return Py_BuildValue("f", (*(float*)ptr));
83
84 case kLong_t:
85 return Py_BuildValue("l", (*(long*)ptr));
86
87 case kULong_t:
88 return Py_BuildValue("k", (*(unsigned long*)ptr));
89
90 case kLong64_t:
91 return Py_BuildValue("L", (*(Long64_t*)ptr));
92
93 case kULong64_t:
94 return Py_BuildValue("K", (*(ULong64_t*)ptr));
95
96 case kCharStar:
97 return Py_BuildValue("s", (*(char**)ptr));
98
99 case kBits:
100 case kOther_t:
101 case kNoType_t:
102 default:
103 return PyUnicode_FromString("(UNKNOWN)");
104 //py_mbr = Py_None;
105 //Py_INCREF(py_mbr);
106 } // switch
107 // not reached.
108 //Py_INCREF(Py_None);
109 //return Py_None;
110}
111
112#if PYROOT_INSPECTOR_DBG
113inline
114std::string to_str(PyObject *obj)
115{
116 PyObject *py_str = PyObject_Str(obj);
117 std::string s = py_str && PyUnicode_Check(py_str)
118 ? PyUnicode_AsUTF8(py_str)
119 : "";
120 Py_XDECREF(py_str);
121 return s;
122}
123#endif
124
125inline
127new_pylist(PyObject *pylist, PyObject *item)
128{
129 PyObject *obj = PySequence_List(pylist);
130 PyList_Append(obj, item);
131 return obj;
132}
133
134void
135recurse_pyinspect(PyObject *pyobj,
136 PyObject *pyobj_name,
137 PyObject *&pystack,
138 bool persistentOnly,
139 bool retvecs)
140{
141 // handle non-pyroot objects
142 if (!TPython::CPPInstance_Check(pyobj)) {
143 PyObject *val = PyTuple_New(2);
144 PyObject *v0 = pyobj_name; Py_INCREF(v0);
145 PyObject *v1 = pyobj; Py_XINCREF(pyobj);
146 PyTuple_SET_ITEM(val, 0, v0);
147 PyTuple_SET_ITEM(val, 1, v1);
148 PyList_Append(pystack, val);
149 Py_DECREF(val);
150 return;
151 }
152
153 static const std::unordered_set<std::string> vecnames {
154 "vector<float>",
155 "std::vector<float>",
156 "vector<double>",
157 "std::vector<double>",
158 "vector<int>",
159 "std::vector<int>",
160 "vector<unsigned int>",
161 "std::vector<unsigned int>",
162 "vector<short>",
163 "std::vector<short>",
164 "vector<unsigned short>",
165 "std::vector<unsigned short>",
166 "vector<char>",
167 "std::vector<char>",
168 "vector<unsigned char>",
169 "std::vector<unsigned char>",
170 "vector<long>",
171 "std::vector<long>",
172 "vector<unsigned long>",
173 "std::vector<unsigned long>",
174 "vector<long long>",
175 "std::vector<long long>",
176 "vector<unsigned long long>",
177 "std::vector<unsigned long long>",
178 };
179 TClass *tcls = RootUtils::objectIsA(pyobj);
180 std::string clsname = tcls ? tcls->GetName() : "";
181 if (0 == tcls ||
182 (tcls->IsTObject() && clsname != "TLorentzVector") ||
183 (retvecs && vecnames.count (clsname) > 0))
184 {
185 // Return the object directly.
186 PyObject *val = PyTuple_New(2);
187 PyObject *v0 = pyobj_name; Py_INCREF(v0);
188 PyObject *v1 = pyobj; Py_XINCREF(pyobj);
189 PyTuple_SET_ITEM(val, 0, v0);
190 PyTuple_SET_ITEM(val, 1, v1);
191 PyList_Append(pystack, val);
192 Py_DECREF(val);
193 return;
194 }
195 void *obj = TPython::CPPInstance_AsVoidPtr(pyobj);
196
197 if (clsname == "string") {
198 std::string *str = (std::string*)obj;
199 PyObject *val= PyTuple_New(2);
200 PyObject *v0 = pyobj_name; Py_INCREF(v0);
201 PyObject *v1 = PyUnicode_FromString(str->c_str());
202 PyTuple_SET_ITEM(val, 0, v0);
203 PyTuple_SET_ITEM(val, 1, v1);
204 PyList_Append(pystack, val);
205 Py_DECREF(val);
206 return;
207 }
208
209 if (clsname.starts_with( "pair<") ||
210 clsname.starts_with( "std::pair<")) {
211 {
212 PyObject *v0 = PyUnicode_FromString("first");
213 PyObject *v1 = PyObject_GetAttrString(pyobj, "first");
214 PyObject *v1_name = ::new_pylist(pyobj_name, v0);
215 recurse_pyinspect(v1, v1_name, pystack, persistentOnly, retvecs);
216 Py_DECREF(v1_name);
217 Py_DECREF(v0);
218 Py_DECREF(v1);
219 }
220
221 {
222 PyObject *v0 = PyUnicode_FromString("second");
223 PyObject *v1 = PyObject_GetAttrString(pyobj, "second");
224 PyObject *v1_name = ::new_pylist(pyobj_name, v0);
225 recurse_pyinspect(v1, v1_name, pystack, persistentOnly, retvecs);
226 Py_DECREF(v1_name);
227 Py_DECREF(v1);
228 Py_DECREF(v0);
229 }
230 return;
231 }
232
233 if (clsname.starts_with("ElementLink<")) {
234 PyObject* key = PyObject_CallMethod (pyobj, "key", nullptr);
235 PyObject* index = PyObject_CallMethod (pyobj, "index", nullptr);
236 PyObject *val= PyTuple_New(3);
237 PyObject *v0 = pyobj_name; Py_INCREF(v0);
238 PyTuple_SET_ITEM(val, 0, v0);
239 PyTuple_SET_ITEM(val, 1, key);
240 PyTuple_SET_ITEM(val, 2, index);
241 PyList_Append(pystack, val);
242 Py_DECREF(val);
243 return;
244 }
245
246 if (clsname.starts_with("DataLink<")) {
247 PyObject* key = PyObject_CallMethod (pyobj, "key", nullptr);
248 PyObject *val= PyTuple_New(2);
249 PyObject *v0 = pyobj_name; Py_INCREF(v0);
250 PyTuple_SET_ITEM(val, 0, v0);
251 PyTuple_SET_ITEM(val, 1, key);
252 PyList_Append(pystack, val);
253 Py_DECREF(val);
254 return;
255 }
256
257 // Most xAOD objects don't have any persistent data members.
258 // Try to detect that and skip the actual iteration over the
259 // container in that case.
260 if (clsname.starts_with( "DataVector<xAOD::")) {
261 TClassEdit::TSplitType split (clsname.c_str());
262 if (split.fElements.size() > 1) {
263 TClass* eltcls = TClass::GetClass (split.fElements[1].c_str());
264 if (eltcls) {
265 TList *members = eltcls->GetListOfDataMembers();
266 const Int_t nmembers = members ? members->GetEntries() : 0;
267 bool nomem = true;
268 for (Int_t j = 0; j<nmembers; ++j) {
269 TDataMember *mbr = (TDataMember*)(members->At(j));
270 if (!persistentOnly || mbr->IsPersistent()) {
271 nomem = false;
272 break;
273 }
274 }
275 if (nomem) {
276 return;
277 }
278 }
279 }
280 }
281
282// PySequence_Check returns true if the class implements __getitem__
283// In newer cppyy versions, e.g. 3+, every class has __getitem__ implemented
284// even if the class does not provide sequence protocol
285// Hence, use the cppyy API CPyCppyy::Sequence_Check(PyObject*) function
286// See ATEAM-974 and root/issues/15161
287 Int_t hdr = 0;
288 if (CPyCppyy::Sequence_Check(pyobj)) {
289 if (clsname == "CLHEP::Hep3Vector" ||
290 clsname == "TLorentzVector" ||
291 clsname == "TVector3")
292 {
293 hdr = 0;
294 } else {
295 hdr = 1;
296 }
297 } else {
298 hdr = 0;
299 }
300
301 TList *members = tcls->GetListOfDataMembers();
302 // members can be null for STL containers in root 6.08.
303 const Int_t nmembers = members ? members->GetEntries() : 0;
304
305#if PYROOT_INSPECTOR_DBG
306 std::cerr << "==[" << clsname << "]== (#mbrs:"
307 << nmembers
308 << " #stl:" << hdr /*PySequence_Size(pyobj)*/
309 << ")...\n";
310#endif
311
312 if (hdr) {
313 // handle collection
314#if PYROOT_INSPECTOR_DBG
315 {
316 const Py_ssize_t nelems = PySequence_Size(pyobj);
317 std::cerr << "== sequence (" << nelems << ")...\n";
318 }
319#endif
320
321 // This used to use PySequence_GetItem.
322 // However, xAOD::MissingETContainer redefines operator[] to do
323 // something completely different, so that didn't work.
324 // Rewriting in terms of iteration avoids this.
325 // .. except that it mysteriously fails (sometimes) for TileCellVec.
326 // .. and if we try to use the iterator interface for vector<char>,
327 // then with python 3, pyroot will try to convert its contents
328 // to a unicode string object, which will likely fail.
329 Py_ssize_t nelems = PySequence_Size(pyobj);
330 if( nelems > 0 ) {
331 // only try iterating if there are elements
332 if (clsname == "TileCellVec" ||
333 clsname == "vector<char>")
334 {
335 for (Py_ssize_t i = 0; i < nelems; ++i) {
336 PyObject *pyidx = PyLong_FromLong(i);
337 PyObject *itr = PySequence_GetItem(pyobj, i);
338 PyObject *itr_name = ::new_pylist(pyobj_name, pyidx);
339 recurse_pyinspect(itr, itr_name, pystack, persistentOnly, retvecs);
340 Py_XDECREF(itr_name);
341 Py_XDECREF(pyidx);
342 Py_XDECREF(itr);
343 }
344 }
345 else {
346 PyObject* iter = PyObject_GetIter(pyobj);
347 size_t i = 0;
348 if (iter) {
349 PyObject* item = nullptr;
350 // Sometimes iterator comparison doesn't work correctly in pyroot.
351 // So protect against overrunning by also counting
352 // the number of elements.
353 while (nelems-- && (item = PyIter_Next(iter))) {
354 PyObject *pyidx = PyLong_FromLong(i++);
355 PyObject *itr_name = ::new_pylist(pyobj_name, pyidx);
356 recurse_pyinspect(item, itr_name, pystack, persistentOnly, retvecs);
357 Py_XDECREF(itr_name);
358 Py_XDECREF(pyidx);
359 Py_DECREF(item);
360 }
361 Py_DECREF(iter);
362 }
363 }
364 }
365
366#if PYROOT_INSPECTOR_DBG
367 std::cerr << "== sequence (" << nelems << ")... [done]\n";
368#endif
369 }
370
371
372 for (Int_t j = 0; j<nmembers; ++j) {
373 TDataMember *mbr = (TDataMember*)(members->At(j));
374 if (mbr->Property() & kIsStatic) continue;
375 Int_t offset = mbr->GetOffset();
376 char *ptr = (char*)obj + offset;
377
378#if PYROOT_INSPECTOR_DBG
379 TClass *mbr_cls = TClass::GetClass(mbr->GetTypeName());
380 std::cerr << "==[" << j << "] - [" << mbr->GetTypeName() << "] "
381 << "[" << mbr->GetName()
382 << "]"
383 << "[" << (mbr_cls ? mbr_cls->GetName() : "N/A") << "]\n";
384#endif
385
386 PyObject *py_mbr_name = 0;
387 PyObject *py_mbr = 0;
388
389 if (persistentOnly && !mbr->IsPersistent())
390 continue;
391 if (mbr->IsaPointer() || std::string(mbr->GetTypeName()).starts_with( "unique_ptr")
392 || std::string(mbr->GetTypeName()).starts_with( "shared_ptr") )
393 continue;
394 if (mbr->IsBasic()) {
395 TDataType * mbr_type = mbr->GetDataType();
396 EDataType mbr_dtype = (EDataType)mbr_type->GetType();
397 py_mbr_name = PyUnicode_FromString(mbr->GetName());
398 py_mbr = to_pyobj(ptr, mbr_dtype);
399 } else if (mbr->IsEnum()) {
400#if PYROOT_INSPECTOR_DBG
401 std::cerr << "==[" << mbr->GetTypeName() << "]["
402 << mbr->GetDataType()->GetType() << "][val="
403 << (*(int*)ptr) << "]["
404 << mbr->GetName() << "] is an enum !!\n";
405#endif
406 py_mbr_name = PyUnicode_FromString(mbr->GetName());
407 py_mbr = to_pyobj(ptr, kInt_t);
408 } else {
409 py_mbr_name = PyUnicode_FromString(mbr->GetName());
410 py_mbr = TPython::CPPInstance_FromVoidPtr((void*)ptr,
411 mbr->GetTypeName());
412 }
413
414 if (!py_mbr || !py_mbr_name) {
415 std::cerr << "could not create py-object of type ["
416 << mbr->GetTypeName() << "] !\n";
417 Py_XDECREF(py_mbr);
418 Py_XDECREF(py_mbr_name);
420 }
421
422 PyObject *this_name = ::new_pylist(pyobj_name, py_mbr_name);
423 recurse_pyinspect(py_mbr, this_name, pystack, persistentOnly, retvecs);
424 Py_DECREF(this_name);
425 Py_DECREF(py_mbr_name);
426 Py_DECREF(py_mbr);
427
428 }
429
430#if PYROOT_INSPECTOR_DBG
431 std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
432 << nmembers << ")... [done]\n";
433#endif
434
435 return;
436}
437
438} // anon-namespace
439
440namespace RootUtils {
441
444 bool persistentOnly /*= false*/)
445{
446 // handle non-pyroot objects
447 if (!TPython::CPPInstance_Check(pyobj)) {
448 Py_XINCREF(pyobj);
449 return pyobj;
450 }
451
452 TClass *tcls = RootUtils::objectIsA(pyobj);
453 if (0 == tcls) {
454 Py_INCREF(Py_None);
455 return Py_None;
456 }
457 void *obj = TPython::CPPInstance_AsVoidPtr(pyobj);
458
459 if (!strcmp(tcls->GetName(), "string")) {
460 std::string *str = (std::string*)obj;
461 return PyUnicode_FromString(str->c_str());
462 }
463
464 TString tstring = tcls->GetName();
465 if (tstring.BeginsWith("pair<") ||
466 tstring.BeginsWith("std::pair<")) {
467 PyObject *val = PyTuple_New(2);
468 PyObject *v0 = PyObject_GetAttrString(pyobj, "first");
469 PyObject *v1 = PyObject_GetAttrString(pyobj, "second");
470 PyTuple_SET_ITEM(val, 0, pyroot_inspect(v0, persistentOnly));
471 PyTuple_SET_ITEM(val, 1, pyroot_inspect(v1, persistentOnly));
472 Py_DECREF(v0);
473 Py_DECREF(v1);
474 return val;
475 }
476
477 Int_t hdr = 0;
478 if (CPyCppyy::Sequence_Check(pyobj)) {
479 if (!strcmp(tcls->GetName(), "CLHEP::Hep3Vector")) {
480 hdr = 0;
481 } else {
482 hdr = 1;
483 }
484 } else {
485 hdr = 0;
486 }
487
488 TList *members = tcls->GetListOfDataMembers();
489 const Int_t nmembers = members->GetEntries();
490
491 PyObject *py_members = PyList_New(nmembers+hdr);
492#if PYROOT_INSPECTOR_DBG
493 std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
494 << nmembers
495 << " #stl:" << hdr /*PySequence_Size(pyobj)*/
496 << " #py-sz:" << PyList_Size(py_members)
497 << ")...\n";
498#endif
499
500 if (hdr) {
501 // handle collection
502 const Py_ssize_t nelems = PySequence_Size(pyobj);
503#if PYROOT_INSPECTOR_DBG
504 std::cerr << "== sequence (" << nelems << ")...\n";
505#endif
506 PyObject *py_elems = PyList_New(nelems);
507 for (Py_ssize_t i = 0; i < nelems; ++i) {
508 PyObject *itr = PySequence_GetItem(pyobj, i);
509 PyObject *itr_pyroot = pyroot_inspect(itr, persistentOnly);
510 PyList_SET_ITEM(py_elems, i, itr_pyroot);
511 Py_DECREF(itr);
512 //Py_DECREF(itr_pyroot);
513 }
514 // add the elements to the "members" list
515 PyList_SET_ITEM(py_members, 0, py_elems);
516#if PYROOT_INSPECTOR_DBG
517 std::cerr << "== sequence (" << nelems << ")... content:\n"
518 << ::to_str(py_elems)
519 << "\n";
520 std::cerr << "== sequence (" << nelems << ")... [done]\n";
521#endif
522 }
523
524
525 for (Int_t j = 0; j<nmembers; ++j) {
526 TDataMember *mbr = (TDataMember*)(members->At(j));
527 Int_t offset = mbr->GetOffset();
528 char *ptr = (char*)obj + offset;
529
530#if PYROOT_INSPECTOR_DBG
531 TClass *mbr_cls = TClass::GetClass(mbr->GetTypeName());
532 std::cerr << "==[" << j << "] - [" << mbr->GetTypeName() << "] "
533 << "[" << mbr->GetName()
534 << "]"
535 << "[" << (mbr_cls ? mbr_cls->GetName() : "N/A") << "]\n";
536#endif
537
538 PyObject *py_mbr = 0;
539
540 if (persistentOnly && !mbr->IsPersistent())
541 continue;
542 if (mbr->IsaPointer())
543 continue;
544 if (mbr->IsBasic()) {
545 TDataType * mbr_type = mbr->GetDataType();
546 EDataType mbr_dtype = (EDataType)mbr_type->GetType();
547 py_mbr = to_pyobj(ptr, mbr_dtype);
548 } else if (mbr->IsEnum()) {
549#if PYROOT_INSPECTOR_DBG
550 std::cerr << "==[" << mbr->GetTypeName() << "]["
551 << mbr->GetDataType()->GetType() << "][val="
552 << (*(int*)ptr) << "]["
553 << mbr->GetName() << "] is an enum !!\n";
554#endif
555 py_mbr = to_pyobj(ptr, kInt_t);
556 } else {
557 PyObject *pyroot_obj = TPython::CPPInstance_FromVoidPtr
558 ((void*)ptr,
559 mbr->GetTypeName());
560 if (pyroot_obj) {
561 py_mbr = pyroot_inspect(pyroot_obj, persistentOnly);
562 }
563 Py_XDECREF(pyroot_obj);
564 }
565 if (!py_mbr) {
566 std::cerr << "could not create py-object of type ["
567 << mbr->GetTypeName() << "] !\n";
568 Py_DECREF(py_members);
570 }
571
572 PyObject *py_item = PyTuple_New(2);
573 PyTuple_SET_ITEM(py_item, 0,
574 PyUnicode_FromString(mbr->GetName()));
575 PyTuple_SET_ITEM(py_item, 1, py_mbr);
576 PyList_SET_ITEM(py_members, j+hdr, py_item);
577 }
578#if PYROOT_INSPECTOR_DBG
579 std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
580 << nmembers << ")... [done]\n";
581#endif
582 return py_members;
583}
584
587 PyObject *pyobj_name,
588 bool persistentOnly /*= false*/,
589 bool retvecs /*= false*/)
590{
591 PyObject *pystack = PyList_New(0);
592 ::recurse_pyinspect(pyobj, pyobj_name, pystack, persistentOnly, retvecs);
593 return pystack;
594}
595
596} // namespace RootUtils
597
_object PyObject
Utility code originally from pyroot.
Define macros for attributes used to control the static checker.
#define ATLAS_NO_CHECK_FILE_THREAD_SAFETY
static PyObject * pyroot_inspect(PyObject *obj, bool persistentOnly=false)
static PyObject * pyroot_inspect2(PyObject *obj, PyObject *obj_name, bool persistentOnly=false, bool retvecs=false)
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
CPyCppyy::PyException PyException
Definition Utility.h:24
TClass * objectIsA(PyObject *obj)
Definition Utility.cxx:103
Definition index.py:1