ATLAS Offline Software
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 
49 namespace {
50 PyObject*
51 to_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
113 inline
114 std::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 
125 inline
126 PyObject*
127 new_pylist(PyObject *pylist, PyObject *item)
128 {
129  PyObject *obj = PySequence_List(pylist);
130  PyList_Append(obj, item);
131  return obj;
132 }
133 
134 void
135 recurse_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);
419  throw RootUtils::PyException();
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 
440 namespace RootUtils {
441 
442 PyObject*
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);
569  throw RootUtils::PyException();
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 
585 PyObject*
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 
RootUtils::PyROOTInspector::pyroot_inspect
static PyObject * pyroot_inspect(PyObject *obj, bool persistentOnly=false)
Definition: PyROOTInspector.cxx:443
RootUtils::PyException
CPyCppyy::PyException PyException
Definition: Utility.h:24
RootUtils
Definition: ILogger.h:20
createLinkingScheme.iter
iter
Definition: createLinkingScheme.py:62
PyROOTInspector.h
index
Definition: index.py:1
Utility.h
Utility code originally from pyroot.
dbg::ptr
void * ptr(T *p)
Definition: SGImplSvc.cxx:74
PyPoolBrowser.item
item
Definition: PyPoolBrowser.py:129
parseMapping.v0
def v0
Definition: parseMapping.py:149
lumiFormat.i
int i
Definition: lumiFormat.py:85
RootUtils::objectIsA
TClass * objectIsA(PyObject *obj)
Definition: Utility.cxx:103
RootUtils::PyROOTInspector::pyroot_inspect2
static PyObject * pyroot_inspect2(PyObject *obj, PyObject *obj_name, bool persistentOnly=false, bool retvecs=false)
Definition: PyROOTInspector.cxx:586
Pythia8_RapidityOrderMPI.val
val
Definition: Pythia8_RapidityOrderMPI.py:14
convertTimingResiduals.offset
offset
Definition: convertTimingResiduals.py:71
python.SystemOfUnits.s
float s
Definition: SystemOfUnits.py:147
str
Definition: BTagTrackIpAccessor.cxx:11
checker_macros.h
Define macros for attributes used to control the static checker.
python.PyAthena.obj
obj
Definition: PyAthena.py:132
PyObject
_object PyObject
Definition: IPyComponent.h:26
ATLAS_NO_CHECK_FILE_THREAD_SAFETY
ATLAS_NO_CHECK_FILE_THREAD_SAFETY
Definition: PyROOTInspector.cxx:11
Trk::split
@ split
Definition: LayerMaterialProperties.h:38
mapkey::key
key
Definition: TElectronEfficiencyCorrectionTool.cxx:37