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