ATLAS Offline Software
PyROOTInspector.cxx
Go to the documentation of this file.
1 /*
2  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3 */
4 
5 // $Id: PyROOTInspector.cxx 790007 2016-12-15 17:46:55Z ssnyder $
6 
7 //#define PYROOT_INSPECTOR_DBG 1
8 #define PYROOT_INSPECTOR_DBG 0
9 
10 // Interacts with python, so only excuted single-threaded (GIL).
13 
14 
15 #include "Python.h"
16 
17 // ROOT includes
18 #include "TClassRef.h"
19 #include "TDataMember.h"
20 #include "TDataType.h"
21 #include "TVirtualCollectionProxy.h"
22 #include "TClassEdit.h"
23 
24 #include "Utility.h"
25 
26 // PyROOT includes
27 #include <TPython.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 // PySequence_Check returns true if the class implements __getitem__
135 // In newer cppyy versions, e.g. 3+, every class has __getitem__ implemented
136 // even if the class does not provide sequence protocol
137 // This is one practical way of dealing with this issue (not strictly 1-to-1)
138 // See ATEAM-974 and root/issues/15161
139 inline
140 bool is_sequence(PyObject *obj)
141 {
142  auto item = PySequence_Size(obj) > 0 ? PySequence_GetItem(obj, 0) : nullptr;
143  if (item) {
144  Py_DECREF(item);
145  return true;
146  }
147  PyErr_Clear();
148  return false;
149 }
150 
151 void
152 recurse_pyinspect(PyObject *pyobj,
153  PyObject *pyobj_name,
154  PyObject *&pystack,
155  bool persistentOnly,
156  bool retvecs)
157 {
158  // handle non-pyroot objects
159  if (!TPython::CPPInstance_Check(pyobj)) {
160  PyObject *val = PyTuple_New(2);
161  PyObject *v0 = pyobj_name; Py_INCREF(v0);
162  PyObject *v1 = pyobj; Py_XINCREF(pyobj);
163  PyTuple_SET_ITEM(val, 0, v0);
164  PyTuple_SET_ITEM(val, 1, v1);
165  PyList_Append(pystack, val);
166  Py_DECREF(val);
167  return;
168  }
169 
170  static const std::unordered_set<std::string> vecnames {
171  "vector<float>",
172  "std::vector<float>",
173  "vector<double>",
174  "std::vector<double>",
175  "vector<int>",
176  "std::vector<int>",
177  "vector<unsigned int>",
178  "std::vector<unsigned int>",
179  "vector<short>",
180  "std::vector<short>",
181  "vector<unsigned short>",
182  "std::vector<unsigned short>",
183  "vector<char>",
184  "std::vector<char>",
185  "vector<unsigned char>",
186  "std::vector<unsigned char>",
187  "vector<long>",
188  "std::vector<long>",
189  "vector<unsigned long>",
190  "std::vector<unsigned long>",
191  "vector<long long>",
192  "std::vector<long long>",
193  "vector<unsigned long long>",
194  "std::vector<unsigned long long>",
195  };
196  TClass *tcls = RootUtils::objectIsA(pyobj);
197  std::string clsname = tcls ? tcls->GetName() : "";
198  if (0 == tcls ||
199  (tcls->IsTObject() && clsname != "TLorentzVector") ||
200  (retvecs && vecnames.count (clsname) > 0))
201  {
202  PyObject *val = PyTuple_New(2);
203  PyObject *v0 = pyobj_name; Py_INCREF(v0);
204  PyObject *v1 = pyobj; Py_XINCREF(pyobj);
205  PyTuple_SET_ITEM(val, 0, v0);
206  PyTuple_SET_ITEM(val, 1, v1);
207  PyList_Append(pystack, val);
208  Py_DECREF(val);
209  return;
210  }
211  void *obj = TPython::CPPInstance_AsVoidPtr(pyobj);
212 
213  if (clsname == "string") {
214  std::string *str = (std::string*)obj;
215  PyObject *val= PyTuple_New(2);
216  PyObject *v0 = pyobj_name; Py_INCREF(v0);
217  PyObject *v1 = PyUnicode_FromString(str->c_str());
218  PyTuple_SET_ITEM(val, 0, v0);
219  PyTuple_SET_ITEM(val, 1, v1);
220  PyList_Append(pystack, val);
221  Py_DECREF(val);
222  return;
223  }
224 
225  if (clsname.starts_with( "pair<") ||
226  clsname.starts_with( "std::pair<")) {
227  {
228  PyObject *v0 = PyUnicode_FromString("first");
229  PyObject *v1 = PyObject_GetAttrString(pyobj, "first");
230  PyObject *v1_name = ::new_pylist(pyobj_name, v0);
231  recurse_pyinspect(v1, v1_name, pystack, persistentOnly, retvecs);
232  Py_DECREF(v1_name);
233  Py_DECREF(v0);
234  Py_DECREF(v1);
235  }
236 
237  {
238  PyObject *v0 = PyUnicode_FromString("second");
239  PyObject *v1 = PyObject_GetAttrString(pyobj, "second");
240  PyObject *v1_name = ::new_pylist(pyobj_name, v0);
241  recurse_pyinspect(v1, v1_name, pystack, persistentOnly, retvecs);
242  Py_DECREF(v1_name);
243  Py_DECREF(v1);
244  Py_DECREF(v0);
245  }
246  return;
247  }
248 
249  // Most xAOD objects don't have any persistent data members.
250  // Try to detect that and skip the actual iteration over the
251  // container in that case.
252  if (clsname.starts_with( "DataVector<xAOD::")) {
253  TClassEdit::TSplitType split (clsname.c_str());
254  if (split.fElements.size() > 1) {
255  TClass* eltcls = TClass::GetClass (split.fElements[1].c_str());
256  if (eltcls) {
257  TList *members = eltcls->GetListOfDataMembers();
258  const Int_t nmembers = members ? members->GetEntries() : 0;
259  bool nomem = true;
260  for (Int_t j = 0; j<nmembers; ++j) {
261  TDataMember *mbr = (TDataMember*)(members->At(j));
262  if (!persistentOnly || mbr->IsPersistent()) {
263  nomem = false;
264  break;
265  }
266  }
267  if (nomem) {
268  return;
269  }
270  }
271  }
272  }
273 
274  Int_t hdr = 0;
275  if (is_sequence(pyobj)) {
276  if (clsname == "CLHEP::Hep3Vector" ||
277  clsname == "TLorentzVector" ||
278  clsname == "TVector3")
279  {
280  hdr = 0;
281  } else {
282  hdr = 1;
283  }
284  } else {
285  hdr = 0;
286  }
287 
288  TList *members = tcls->GetListOfDataMembers();
289  // members can be null for STL containers in root 6.08.
290  const Int_t nmembers = members ? members->GetEntries() : 0;
291 
292 #if PYROOT_INSPECTOR_DBG
293  std::cerr << "==[" << clsname << "]== (#mbrs:"
294  << nmembers
295  << " #stl:" << hdr /*PySequence_Size(pyobj)*/
296  << ")...\n";
297 #endif
298 
299  if (hdr) {
300  // handle collection
301 #if PYROOT_INSPECTOR_DBG
302  {
303  const Py_ssize_t nelems = PySequence_Size(pyobj);
304  std::cerr << "== sequence (" << nelems << ")...\n";
305  }
306 #endif
307 
308  // This used to use PySequence_GetItem.
309  // However, xAOD::MissingETContainer redefines operator[] to do
310  // something completely different, so that didn't work.
311  // Rewriting in terms of iteration avoids this.
312  // .. except that it mysteriously fails (sometimes) for TileCellVec.
313  // .. and if we try to use the iterator interface for vector<char>,
314  // then with python 3, pyroot will try to convert its contents
315  // to a unicode string object, which will likely fail.
316  Py_ssize_t nelems = PySequence_Size(pyobj);
317  if (clsname == "TileCellVec" ||
318  clsname == "vector<char>")
319  {
320  for (Py_ssize_t i = 0; i < nelems; ++i) {
321  PyObject *pyidx = PyLong_FromLong(i);
322  PyObject *itr = PySequence_GetItem(pyobj, i);
323  PyObject *itr_name = ::new_pylist(pyobj_name, pyidx);
324  recurse_pyinspect(itr, itr_name, pystack, persistentOnly, retvecs);
325  Py_XDECREF(itr_name);
326  Py_XDECREF(pyidx);
327  Py_XDECREF(itr);
328  }
329  }
330  else {
331  PyObject* iter = PyObject_GetIter(pyobj);
332  size_t i = 0;
333  if (iter) {
334  PyObject* item = nullptr;
335  // Sometimes iterator comparison doesn't work correctly in pyroot.
336  // So protect against overrunning by also counting
337  // the number of elements.
338  while (nelems-- && (item = PyIter_Next(iter))) {
339  PyObject *pyidx = PyLong_FromLong(i++);
340  PyObject *itr_name = ::new_pylist(pyobj_name, pyidx);
341  recurse_pyinspect(item, itr_name, pystack, persistentOnly, retvecs);
342  Py_XDECREF(itr_name);
343  Py_XDECREF(pyidx);
344  Py_DECREF(item);
345  }
346  Py_DECREF(iter);
347  }
348  }
349 
350 #if PYROOT_INSPECTOR_DBG
351  std::cerr << "== sequence (" << nelems << ")... [done]\n";
352 #endif
353  }
354 
355 
356  for (Int_t j = 0; j<nmembers; ++j) {
357  TDataMember *mbr = (TDataMember*)(members->At(j));
358  if (mbr->Property() & kIsStatic) continue;
359  Int_t offset = mbr->GetOffset();
360  char *ptr = (char*)obj + offset;
361 
362 #if PYROOT_INSPECTOR_DBG
363  TClass *mbr_cls = TClass::GetClass(mbr->GetTypeName());
364  std::cerr << "==[" << j << "] - [" << mbr->GetTypeName() << "] "
365  << "[" << mbr->GetName()
366  << "]"
367  << "[" << (mbr_cls ? mbr_cls->GetName() : "N/A") << "]\n";
368 #endif
369 
370  PyObject *py_mbr_name = 0;
371  PyObject *py_mbr = 0;
372 
373  if (persistentOnly && !mbr->IsPersistent())
374  continue;
375  if (mbr->IsaPointer() || std::string(mbr->GetTypeName()).starts_with( "unique_ptr")
376  || std::string(mbr->GetTypeName()).starts_with( "shared_ptr") )
377  continue;
378  if (mbr->IsBasic()) {
379  TDataType * mbr_type = mbr->GetDataType();
380  EDataType mbr_dtype = (EDataType)mbr_type->GetType();
381  py_mbr_name = PyUnicode_FromString(mbr->GetName());
382  py_mbr = to_pyobj(ptr, mbr_dtype);
383  } else if (mbr->IsEnum()) {
384 #if PYROOT_INSPECTOR_DBG
385  std::cerr << "==[" << mbr->GetTypeName() << "]["
386  << mbr->GetDataType()->GetType() << "][val="
387  << (*(int*)ptr) << "]["
388  << mbr->GetName() << "] is an enum !!\n";
389 #endif
390  py_mbr_name = PyUnicode_FromString(mbr->GetName());
391  py_mbr = to_pyobj(ptr, kInt_t);
392  } else {
393  py_mbr_name = PyUnicode_FromString(mbr->GetName());
394  py_mbr = TPython::CPPInstance_FromVoidPtr((void*)ptr,
395  mbr->GetTypeName());
396  }
397 
398  if (!py_mbr || !py_mbr_name) {
399  std::cerr << "could not create py-object of type ["
400  << mbr->GetTypeName() << "] !\n";
401  Py_XDECREF(py_mbr);
402  Py_XDECREF(py_mbr_name);
403  throw RootUtils::PyException();
404  }
405 
406  PyObject *this_name = ::new_pylist(pyobj_name, py_mbr_name);
407  recurse_pyinspect(py_mbr, this_name, pystack, persistentOnly, retvecs);
408  Py_DECREF(this_name);
409  Py_DECREF(py_mbr_name);
410  Py_DECREF(py_mbr);
411 
412  }
413 
414 #if PYROOT_INSPECTOR_DBG
415  std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
416  << nmembers << ")... [done]\n";
417 #endif
418 
419  return;
420 }
421 
422 } // anon-namespace
423 
424 namespace RootUtils {
425 
426 PyObject*
428  bool persistentOnly /*= false*/)
429 {
430  // handle non-pyroot objects
431  if (!TPython::CPPInstance_Check(pyobj)) {
432  Py_XINCREF(pyobj);
433  return pyobj;
434  }
435 
436  TClass *tcls = RootUtils::objectIsA(pyobj);
437  if (0 == tcls) {
438  Py_INCREF(Py_None);
439  return Py_None;
440  }
441  void *obj = TPython::CPPInstance_AsVoidPtr(pyobj);
442 
443  if (!strcmp(tcls->GetName(), "string")) {
444  std::string *str = (std::string*)obj;
445  return PyUnicode_FromString(str->c_str());
446  }
447 
448  TString tstring = tcls->GetName();
449  if (tstring.BeginsWith("pair<") ||
450  tstring.BeginsWith("std::pair<")) {
451  PyObject *val = PyTuple_New(2);
452  PyObject *v0 = PyObject_GetAttrString(pyobj, "first");
453  PyObject *v1 = PyObject_GetAttrString(pyobj, "second");
454  PyTuple_SET_ITEM(val, 0, pyroot_inspect(v0, persistentOnly));
455  PyTuple_SET_ITEM(val, 1, pyroot_inspect(v1, persistentOnly));
456  Py_DECREF(v0);
457  Py_DECREF(v1);
458  return val;
459  }
460 
461  Int_t hdr = 0;
462  if (is_sequence(pyobj)) {
463  if (!strcmp(tcls->GetName(), "CLHEP::Hep3Vector")) {
464  hdr = 0;
465  } else {
466  hdr = 1;
467  }
468  } else {
469  hdr = 0;
470  }
471 
472  TList *members = tcls->GetListOfDataMembers();
473  const Int_t nmembers = members->GetEntries();
474 
475  PyObject *py_members = PyList_New(nmembers+hdr);
476 #if PYROOT_INSPECTOR_DBG
477  std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
478  << nmembers
479  << " #stl:" << hdr /*PySequence_Size(pyobj)*/
480  << " #py-sz:" << PyList_Size(py_members)
481  << ")...\n";
482 #endif
483 
484  if (hdr) {
485  // handle collection
486  const Py_ssize_t nelems = PySequence_Size(pyobj);
487 #if PYROOT_INSPECTOR_DBG
488  std::cerr << "== sequence (" << nelems << ")...\n";
489 #endif
490  PyObject *py_elems = PyList_New(nelems);
491  for (Py_ssize_t i = 0; i < nelems; ++i) {
492  PyObject *itr = PySequence_GetItem(pyobj, i);
493  PyObject *itr_pyroot = pyroot_inspect(itr, persistentOnly);
494  PyList_SET_ITEM(py_elems, i, itr_pyroot);
495  Py_DECREF(itr);
496  //Py_DECREF(itr_pyroot);
497  }
498  // add the elements to the "members" list
499  PyList_SET_ITEM(py_members, 0, py_elems);
500 #if PYROOT_INSPECTOR_DBG
501  std::cerr << "== sequence (" << nelems << ")... content:\n"
502  << ::to_str(py_elems)
503  << "\n";
504  std::cerr << "== sequence (" << nelems << ")... [done]\n";
505 #endif
506  }
507 
508 
509  for (Int_t j = 0; j<nmembers; ++j) {
510  TDataMember *mbr = (TDataMember*)(members->At(j));
511  Int_t offset = mbr->GetOffset();
512  char *ptr = (char*)obj + offset;
513 
514 #if PYROOT_INSPECTOR_DBG
515  TClass *mbr_cls = TClass::GetClass(mbr->GetTypeName());
516  std::cerr << "==[" << j << "] - [" << mbr->GetTypeName() << "] "
517  << "[" << mbr->GetName()
518  << "]"
519  << "[" << (mbr_cls ? mbr_cls->GetName() : "N/A") << "]\n";
520 #endif
521 
522  PyObject *py_mbr = 0;
523 
524  if (persistentOnly && !mbr->IsPersistent())
525  continue;
526  if (mbr->IsaPointer())
527  continue;
528  if (mbr->IsBasic()) {
529  TDataType * mbr_type = mbr->GetDataType();
530  EDataType mbr_dtype = (EDataType)mbr_type->GetType();
531  py_mbr = to_pyobj(ptr, mbr_dtype);
532  } else if (mbr->IsEnum()) {
533 #if PYROOT_INSPECTOR_DBG
534  std::cerr << "==[" << mbr->GetTypeName() << "]["
535  << mbr->GetDataType()->GetType() << "][val="
536  << (*(int*)ptr) << "]["
537  << mbr->GetName() << "] is an enum !!\n";
538 #endif
539  py_mbr = to_pyobj(ptr, kInt_t);
540  } else {
541  PyObject *pyroot_obj = TPython::CPPInstance_FromVoidPtr
542  ((void*)ptr,
543  mbr->GetTypeName());
544  if (pyroot_obj) {
545  py_mbr = pyroot_inspect(pyroot_obj, persistentOnly);
546  }
547  Py_XDECREF(pyroot_obj);
548  }
549  if (!py_mbr) {
550  std::cerr << "could not create py-object of type ["
551  << mbr->GetTypeName() << "] !\n";
552  Py_DECREF(py_members);
553  throw RootUtils::PyException();
554  }
555 
556  PyObject *py_item = PyTuple_New(2);
557  PyTuple_SET_ITEM(py_item, 0,
558  PyUnicode_FromString(mbr->GetName()));
559  PyTuple_SET_ITEM(py_item, 1, py_mbr);
560  PyList_SET_ITEM(py_members, j+hdr, py_item);
561  }
562 #if PYROOT_INSPECTOR_DBG
563  std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
564  << nmembers << ")... [done]\n";
565 #endif
566  return py_members;
567 }
568 
569 PyObject*
571  PyObject *pyobj_name,
572  bool persistentOnly /*= false*/,
573  bool retvecs /*= false*/)
574 {
575  PyObject *pystack = PyList_New(0);
576  ::recurse_pyinspect(pyobj, pyobj_name, pystack, persistentOnly, retvecs);
577  return pystack;
578 }
579 
580 } // namespace RootUtils
581 
RootUtils::PyROOTInspector::pyroot_inspect
static PyObject * pyroot_inspect(PyObject *obj, bool persistentOnly=false)
Definition: PyROOTInspector.cxx:427
RootUtils::PyException
CPyCppyy::PyException PyException
Definition: Utility.h:24
RootUtils
Definition: ILogger.h:20
python.SystemOfUnits.s
int s
Definition: SystemOfUnits.py:131
PyROOTInspector.h
Utility.h
Utility code originally from pyroot.
dbg::ptr
void * ptr(T *p)
Definition: SGImplSvc.cxx:74
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:570
item
Definition: ItemListSvc.h:43
Pythia8_RapidityOrderMPI.val
val
Definition: Pythia8_RapidityOrderMPI.py:14
convertTimingResiduals.offset
offset
Definition: convertTimingResiduals.py:71
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:12
Trk::split
@ split
Definition: LayerMaterialProperties.h:38