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