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 clsname == "vector<signed char>")
335 {
336 for (Py_ssize_t i = 0; i < nelems; ++i) {
337 PyObject *pyidx = PyLong_FromLong(i);
338 PyObject *itr = PySequence_GetItem(pyobj, i);
339 PyObject *itr_name = ::new_pylist(pyobj_name, pyidx);
340 recurse_pyinspect(itr, itr_name, pystack, persistentOnly, retvecs);
341 Py_XDECREF(itr_name);
342 Py_XDECREF(pyidx);
343 Py_XDECREF(itr);
344 }
345 }
346 else {
347 PyObject* iter = PyObject_GetIter(pyobj);
348 size_t i = 0;
349 if (iter) {
350 PyObject* item = nullptr;
351 // Sometimes iterator comparison doesn't work correctly in pyroot.
352 // So protect against overrunning by also counting
353 // the number of elements.
354 while (nelems-- && (item = PyIter_Next(iter))) {
355 PyObject *pyidx = PyLong_FromLong(i++);
356 PyObject *itr_name = ::new_pylist(pyobj_name, pyidx);
357 recurse_pyinspect(item, itr_name, pystack, persistentOnly, retvecs);
358 Py_XDECREF(itr_name);
359 Py_XDECREF(pyidx);
360 Py_DECREF(item);
361 }
362 Py_DECREF(iter);
363 }
364 }
365 }
366
367#if PYROOT_INSPECTOR_DBG
368 std::cerr << "== sequence (" << nelems << ")... [done]\n";
369#endif
370 }
371
372
373 for (Int_t j = 0; j<nmembers; ++j) {
374 TDataMember *mbr = (TDataMember*)(members->At(j));
375 if (mbr->Property() & kIsStatic) continue;
376 Int_t offset = mbr->GetOffset();
377 char *ptr = (char*)obj + offset;
378
379#if PYROOT_INSPECTOR_DBG
380 TClass *mbr_cls = TClass::GetClass(mbr->GetTypeName());
381 std::cerr << "==[" << j << "] - [" << mbr->GetTypeName() << "] "
382 << "[" << mbr->GetName()
383 << "]"
384 << "[" << (mbr_cls ? mbr_cls->GetName() : "N/A") << "]\n";
385#endif
386
387 PyObject *py_mbr_name = 0;
388 PyObject *py_mbr = 0;
389
390 if (persistentOnly && !mbr->IsPersistent())
391 continue;
392 if (mbr->IsaPointer() || std::string(mbr->GetTypeName()).starts_with( "unique_ptr")
393 || std::string(mbr->GetTypeName()).starts_with( "shared_ptr") )
394 continue;
395 if (mbr->IsBasic()) {
396 TDataType * mbr_type = mbr->GetDataType();
397 EDataType mbr_dtype = (EDataType)mbr_type->GetType();
398 py_mbr_name = PyUnicode_FromString(mbr->GetName());
399 py_mbr = to_pyobj(ptr, mbr_dtype);
400 } else if (mbr->IsEnum()) {
401#if PYROOT_INSPECTOR_DBG
402 std::cerr << "==[" << mbr->GetTypeName() << "]["
403 << mbr->GetDataType()->GetType() << "][val="
404 << (*(int*)ptr) << "]["
405 << mbr->GetName() << "] is an enum !!\n";
406#endif
407 py_mbr_name = PyUnicode_FromString(mbr->GetName());
408 py_mbr = to_pyobj(ptr, kInt_t);
409 } else {
410 py_mbr_name = PyUnicode_FromString(mbr->GetName());
411 py_mbr = TPython::CPPInstance_FromVoidPtr((void*)ptr,
412 mbr->GetTypeName());
413 }
414
415 if (!py_mbr || !py_mbr_name) {
416 std::cerr << "could not create py-object of type ["
417 << mbr->GetTypeName() << "] !\n";
418 Py_XDECREF(py_mbr);
419 Py_XDECREF(py_mbr_name);
421 }
422
423 PyObject *this_name = ::new_pylist(pyobj_name, py_mbr_name);
424 recurse_pyinspect(py_mbr, this_name, pystack, persistentOnly, retvecs);
425 Py_DECREF(this_name);
426 Py_DECREF(py_mbr_name);
427 Py_DECREF(py_mbr);
428
429 }
430
431#if PYROOT_INSPECTOR_DBG
432 std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
433 << nmembers << ")... [done]\n";
434#endif
435
436 return;
437}
438
439} // anon-namespace
440
441namespace RootUtils {
442
445 bool persistentOnly /*= false*/)
446{
447 // handle non-pyroot objects
448 if (!TPython::CPPInstance_Check(pyobj)) {
449 Py_XINCREF(pyobj);
450 return pyobj;
451 }
452
453 TClass *tcls = RootUtils::objectIsA(pyobj);
454 if (0 == tcls) {
455 Py_INCREF(Py_None);
456 return Py_None;
457 }
458 void *obj = TPython::CPPInstance_AsVoidPtr(pyobj);
459
460 if (!strcmp(tcls->GetName(), "string")) {
461 std::string *str = (std::string*)obj;
462 return PyUnicode_FromString(str->c_str());
463 }
464
465 TString tstring = tcls->GetName();
466 if (tstring.BeginsWith("pair<") ||
467 tstring.BeginsWith("std::pair<")) {
468 PyObject *val = PyTuple_New(2);
469 PyObject *v0 = PyObject_GetAttrString(pyobj, "first");
470 PyObject *v1 = PyObject_GetAttrString(pyobj, "second");
471 PyTuple_SET_ITEM(val, 0, pyroot_inspect(v0, persistentOnly));
472 PyTuple_SET_ITEM(val, 1, pyroot_inspect(v1, persistentOnly));
473 Py_DECREF(v0);
474 Py_DECREF(v1);
475 return val;
476 }
477
478 Int_t hdr = 0;
479 if (CPyCppyy::Sequence_Check(pyobj)) {
480 if (!strcmp(tcls->GetName(), "CLHEP::Hep3Vector")) {
481 hdr = 0;
482 } else {
483 hdr = 1;
484 }
485 } else {
486 hdr = 0;
487 }
488
489 TList *members = tcls->GetListOfDataMembers();
490 const Int_t nmembers = members->GetEntries();
491
492 PyObject *py_members = PyList_New(nmembers+hdr);
493#if PYROOT_INSPECTOR_DBG
494 std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
495 << nmembers
496 << " #stl:" << hdr /*PySequence_Size(pyobj)*/
497 << " #py-sz:" << PyList_Size(py_members)
498 << ")...\n";
499#endif
500
501 if (hdr) {
502 // handle collection
503 const Py_ssize_t nelems = PySequence_Size(pyobj);
504#if PYROOT_INSPECTOR_DBG
505 std::cerr << "== sequence (" << nelems << ")...\n";
506#endif
507 PyObject *py_elems = PyList_New(nelems);
508 for (Py_ssize_t i = 0; i < nelems; ++i) {
509 PyObject *itr = PySequence_GetItem(pyobj, i);
510 PyObject *itr_pyroot = pyroot_inspect(itr, persistentOnly);
511 PyList_SET_ITEM(py_elems, i, itr_pyroot);
512 Py_DECREF(itr);
513 //Py_DECREF(itr_pyroot);
514 }
515 // add the elements to the "members" list
516 PyList_SET_ITEM(py_members, 0, py_elems);
517#if PYROOT_INSPECTOR_DBG
518 std::cerr << "== sequence (" << nelems << ")... content:\n"
519 << ::to_str(py_elems)
520 << "\n";
521 std::cerr << "== sequence (" << nelems << ")... [done]\n";
522#endif
523 }
524
525
526 for (Int_t j = 0; j<nmembers; ++j) {
527 TDataMember *mbr = (TDataMember*)(members->At(j));
528 Int_t offset = mbr->GetOffset();
529 char *ptr = (char*)obj + offset;
530
531#if PYROOT_INSPECTOR_DBG
532 TClass *mbr_cls = TClass::GetClass(mbr->GetTypeName());
533 std::cerr << "==[" << j << "] - [" << mbr->GetTypeName() << "] "
534 << "[" << mbr->GetName()
535 << "]"
536 << "[" << (mbr_cls ? mbr_cls->GetName() : "N/A") << "]\n";
537#endif
538
539 PyObject *py_mbr = 0;
540
541 if (persistentOnly && !mbr->IsPersistent())
542 continue;
543 if (mbr->IsaPointer())
544 continue;
545 if (mbr->IsBasic()) {
546 TDataType * mbr_type = mbr->GetDataType();
547 EDataType mbr_dtype = (EDataType)mbr_type->GetType();
548 py_mbr = to_pyobj(ptr, mbr_dtype);
549 } else if (mbr->IsEnum()) {
550#if PYROOT_INSPECTOR_DBG
551 std::cerr << "==[" << mbr->GetTypeName() << "]["
552 << mbr->GetDataType()->GetType() << "][val="
553 << (*(int*)ptr) << "]["
554 << mbr->GetName() << "] is an enum !!\n";
555#endif
556 py_mbr = to_pyobj(ptr, kInt_t);
557 } else {
558 PyObject *pyroot_obj = TPython::CPPInstance_FromVoidPtr
559 ((void*)ptr,
560 mbr->GetTypeName());
561 if (pyroot_obj) {
562 py_mbr = pyroot_inspect(pyroot_obj, persistentOnly);
563 }
564 Py_XDECREF(pyroot_obj);
565 }
566 if (!py_mbr) {
567 std::cerr << "could not create py-object of type ["
568 << mbr->GetTypeName() << "] !\n";
569 Py_DECREF(py_members);
571 }
572
573 PyObject *py_item = PyTuple_New(2);
574 PyTuple_SET_ITEM(py_item, 0,
575 PyUnicode_FromString(mbr->GetName()));
576 PyTuple_SET_ITEM(py_item, 1, py_mbr);
577 PyList_SET_ITEM(py_members, j+hdr, py_item);
578 }
579#if PYROOT_INSPECTOR_DBG
580 std::cerr << "==[" << tcls->GetName() << "]== (#mbrs:"
581 << nmembers << ")... [done]\n";
582#endif
583 return py_members;
584}
585
588 PyObject *pyobj_name,
589 bool persistentOnly /*= false*/,
590 bool retvecs /*= false*/)
591{
592 PyObject *pystack = PyList_New(0);
593 ::recurse_pyinspect(pyobj, pyobj_name, pystack, persistentOnly, retvecs);
594 return pystack;
595}
596
597} // namespace RootUtils
598
_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
CPyCppyy::PyException PyException
Definition Utility.h:24
TClass * objectIsA(PyObject *obj)
Definition Utility.cxx:103
Definition index.py:1