Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
TTreePyz.cxx
Go to the documentation of this file.
1// Author: Enric Tejedor CERN 06/2018
2// Original PyROOT code by Wim Lavrijsen, LBL
3
4/*************************************************************************
5 * Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
6 * All rights reserved. *
7 * *
8 * For the licensing terms see $ROOTSYS/LICENSE. *
9 * For the list of contributors see $ROOTSYS/README/CREDITS. *
10 *************************************************************************/
11
12// Bindings
13#include <Python.h>
14
15// TODO: refactor public CPyCppyy API such that this forward declaration is not
16// needed anymore. Including "CPyCppyy/API.h" should be enough.
17namespace CPyCppyy {
18typedef Py_ssize_t dim_t;
19} // namespace CPyCppyy
20
25
26#include "CPyCppyy/API.h"
27
28#include "PyROOTPythonize.h"
29
30// ROOT
31#include "TClass.h"
32#include "TTree.h"
33#include "TBranch.h"
34#include "TBranchElement.h"
35#include "TBranchObject.h"
36#include "TLeaf.h"
37#include "TLeafElement.h"
38#include "TLeafObject.h"
39#include "TStreamerElement.h"
40#include "TStreamerInfo.h"
41
42#include <algorithm>
43#include <sstream>
44
45namespace {
46
47// Get the TClass of the C++ object proxied by pyobj
48TClass *GetTClass(PyObject *pyobj)
49{
51}
52
53} // namespace
54
55using namespace CPyCppyy;
56
57namespace PyROOT{
58void GetBuffer(PyObject *pyobject, void *&buf);
59}
60
61static TBranch *SearchForBranch(TTree *tree, const char *name)
62{
63 TBranch *branch = tree->GetBranch(name);
64 if (!branch) {
65 // for benefit of naming of sub-branches, the actual name may have a trailing '.'
66 branch = tree->GetBranch((std::string(name) + '.').c_str());
67 }
68 return branch;
69}
70
71static TLeaf *SearchForLeaf(TTree *tree, const char *name, TBranch *branch)
72{
73 TLeaf *leaf = tree->GetLeaf(name);
74 if (branch && !leaf) {
75 leaf = branch->GetLeaf(name);
76 if (!leaf) {
77 TObjArray *leaves = branch->GetListOfLeaves();
78 if (leaves->GetSize() && (leaves->First() == leaves->Last())) {
79 // i.e., if unambiguously only this one
80 leaf = (TLeaf *)leaves->At(0);
81 }
82 }
83 }
84 return leaf;
85}
86
87static std::pair<void *, std::string> ResolveBranch(TTree *tree, const char *name, TBranch *branch)
88{
89 // for partial return of a split object
90 if (branch->InheritsFrom(TBranchElement::Class())) {
91 TBranchElement *be = (TBranchElement *)branch;
92 if (be->GetCurrentClass() && (be->GetCurrentClass() != be->GetTargetClass()) && (0 <= be->GetID())) {
93 Long_t offset = ((TStreamerElement *)be->GetInfo()->GetElements()->At(be->GetID()))->GetOffset();
94 return {be->GetObject() + offset, be->GetCurrentClass()->GetName()};
95 }
96 }
97
98 // for return of a full object
99 if (branch->IsA() == TBranchElement::Class() || branch->IsA() == TBranchObject::Class()) {
100 if (branch->GetAddress())
101 return {*(void **)branch->GetAddress(), branch->GetClassName()};
102
103 // try leaf, otherwise indicate failure by returning a typed null-object
104 TObjArray *leaves = branch->GetListOfLeaves();
105 if (!tree->GetLeaf(name) && !(leaves->GetSize() && (leaves->First() == leaves->Last())))
106 return {nullptr, branch->GetClassName()};
107 }
108
109 return {nullptr, ""};
110}
111
112/**
113 * @brief Extracts static dimensions from the title of a TLeaf object.
114 *
115 * The function assumes that the title of the TLeaf object contains dimensions
116 * in the format `[dim1][dim2]...`.
117 *
118 * @note In the current implementation of TLeaf, there is no way to extract the
119 * dimensions without string parsing.
120 *
121 * @param title title of the TLeaf object from which to extract dimensions.
122 * @return std::vector<dim_t> A vector containing the extracted dimensions.
123 */
124static std::vector<dim_t> getMultiDims(std::string const &title)
125{
126 std::vector<dim_t> dims;
127 std::stringstream ss{title};
128
129 while (ss.good()) {
130 std::string substr;
131 getline(ss, substr, '[');
132 getline(ss, substr, ']');
133 if (!substr.empty()) {
134 dims.push_back(std::stoi(substr));
135 }
136 }
137
138 return dims;
139}
140
141static PyObject *WrapLeaf(TLeaf *leaf)
142{
143 if (1 < leaf->GetLenStatic() || leaf->GetLeafCount()) {
144 bool isStatic = 1 < leaf->GetLenStatic();
145 // array types
146 std::string typeName = leaf->GetTypeName();
147 std::vector<dim_t> dimsVec{leaf->GetNdata()};
148 std::string title = leaf->GetTitle();
149 // Multidimensional array case
150 if (std::count(title.begin(), title.end(), '[') >= 2) {
151 dimsVec = getMultiDims(title);
152 }
153 CPyCppyy::Dimensions dims{static_cast<dim_t>(dimsVec.size()), dimsVec.data()};
154 Converter *pcnv = CreateConverter(typeName + (isStatic ? "[]" : "*"), dims);
155
156 void *address = 0;
157 if (leaf->GetBranch())
158 address = (void *)leaf->GetBranch()->GetAddress();
159 if (!address)
160 address = (void *)leaf->GetValuePointer();
161
162 PyObject *value = pcnv->FromMemory(&address);
164
165 return value;
166 } else if (leaf->GetValuePointer()) {
167 // value types
168 Converter *pcnv = CreateConverter(leaf->GetTypeName());
169 PyObject *value = 0;
170 if (leaf->IsA() == TLeafElement::Class() || leaf->IsA() == TLeafObject::Class())
171 value = pcnv->FromMemory((void *)*(void **)leaf->GetValuePointer());
172 else
173 value = pcnv->FromMemory((void *)leaf->GetValuePointer());
175
176 return value;
177 }
178
179 return nullptr;
180}
181
182// Allow access to branches/leaves as if they were data members Returns a
183// Python tuple where the first element is either the desired CPyCppyy proxy,
184// or an address that still needs to be wrapped by the caller in a proxy using
185// cppyy.ll.cast. In the latter case, the second tuple element is the target
186// type name. Otherwise, the second element is an empty string.
188{
189 PyObject *self = nullptr;
190 PyObject *pyname = nullptr;
191
192 PyArg_ParseTuple(args, "OU:GetBranchAttr", &self, &pyname);
193
194 const char *name_possibly_alias = PyUnicode_AsUTF8AndSize(pyname, nullptr);
195 if (!name_possibly_alias)
196 return nullptr;
197
198 // get hold of actual tree
199 auto tree = (TTree *)GetTClass(self)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(self));
200
201 if (!tree) {
202 PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
203 return 0;
204 }
205
206 // deal with possible aliasing
207 const char *name = tree->GetAlias(name_possibly_alias);
208 if (!name)
209 name = name_possibly_alias;
210
211 // search for branch first (typical for objects)
212 TBranch *branch = SearchForBranch(tree, name);
213
214 if (branch) {
215 // found a branched object, wrap its address for the object it represents
216 const auto [finalAddressVoidPtr, finalTypeName] = ResolveBranch(tree, name, branch);
217 if (!finalTypeName.empty()) {
218 PyObject *outTuple = PyTuple_New(2);
219 PyTuple_SetItem(outTuple, 0, PyLong_FromLongLong((intptr_t)finalAddressVoidPtr));
220 PyTuple_SetItem(outTuple, 1, PyUnicode_FromString((finalTypeName + "*").c_str()));
221 return outTuple;
222 }
223 }
224
225 // if not, try leaf
226 if (TLeaf *leaf = SearchForLeaf(tree, name, branch)) {
227 // found a leaf, extract value and wrap with a Python object according to its type
228 auto wrapper = WrapLeaf(leaf);
229 if (wrapper != nullptr) {
230 PyObject *outTuple = PyTuple_New(2);
231 PyTuple_SetItem(outTuple, 0, wrapper);
232 PyTuple_SetItem(outTuple, 1, PyUnicode_FromString(""));
233 return outTuple;
234 }
235 }
236
237 // confused
238 PyErr_Format(PyExc_AttributeError, "\'%s\' object has no attribute \'%s\'", tree->IsA()->GetName(), name);
239 return 0;
240}
241
242////////////////////////////////////////////////////////////////////////////
243/// Try to match the arguments of TTree::Branch to the following overload:
244/// - ( const char*, void*, const char*, Int_t = 32000 )
245/// If the match succeeds, invoke Branch on the C++ tree with the right
246/// arguments.
248{
249 PyObject *treeObj = nullptr;
250 PyObject *name = nullptr, *address = nullptr, *leaflist = nullptr, *bufsize = nullptr;
251
252 if (PyArg_ParseTuple(args, "OO!OO!|O!:Branch", &treeObj, &PyUnicode_Type, &name, &address, &PyUnicode_Type,
253 &leaflist, &PyLong_Type, &bufsize)) {
254
255 auto tree = (TTree *)GetTClass(treeObj)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(treeObj));
256 if (!tree) {
257 PyErr_SetString(PyExc_TypeError, "TTree::Branch must be called with a TTree instance as first argument");
258 return nullptr;
259 }
260
261 void *buf = nullptr;
262 if (CPyCppyy::Instance_Check(address))
263 buf = CPyCppyy::Instance_AsVoidPtr(address);
264 else
265 PyROOT::GetBuffer(address, buf);
266
267 if (buf) {
268 TBranch *branch = nullptr;
269 const char *nameString = PyUnicode_AsUTF8AndSize(name, nullptr);
270 if (!nameString) {
271 return nullptr;
272 }
273 const char *leaflistString = PyUnicode_AsUTF8AndSize(leaflist, nullptr);
274 if (!leaflistString) {
275 return nullptr;
276 }
277 if (argc == 5) {
278 branch = tree->Branch(nameString, buf, leaflistString, PyLong_AsLong(bufsize));
279 } else {
280 branch = tree->Branch(nameString, buf, leaflistString);
281 }
282
283 return BindCppObject(branch, Cppyy::GetScope("TBranch"));
284 }
285 }
286 PyErr_Clear();
287
289}
290
291////////////////////////////////////////////////////////////////////////////
292/// Try to match the arguments of TTree::Branch to one of the following
293/// overloads:
294/// - ( const char*, const char*, T**, Int_t = 32000, Int_t = 99 )
295/// - ( const char*, T**, Int_t = 32000, Int_t = 99 )
296/// If the match succeeds, invoke Branch on the C++ tree with the right
297/// arguments.
299{
300 PyObject *treeObj = nullptr;
301 PyObject *name = nullptr, *clName = nullptr, *address = nullptr, *bufsize = nullptr, *splitlevel = nullptr;
302
303 auto bIsMatch = false;
304 if (PyArg_ParseTuple(args, "OO!O!O|O!O!:Branch", &treeObj, &PyUnicode_Type, &name, &PyUnicode_Type, &clName,
305 &address, &PyLong_Type, &bufsize, &PyLong_Type, &splitlevel)) {
306 bIsMatch = true;
307 } else {
308 PyErr_Clear();
309 if (PyArg_ParseTuple(args, "OO!O|O!O!", &treeObj, &PyUnicode_Type, &name, &address, &PyLong_Type, &bufsize,
310 &PyLong_Type, &splitlevel)) {
311 bIsMatch = true;
312 } else {
313 PyErr_Clear();
314 }
315 }
316
317 if (bIsMatch) {
318 auto tree = (TTree *)GetTClass(treeObj)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(treeObj));
319 if (!tree) {
320 PyErr_SetString(PyExc_TypeError, "TTree::Branch must be called with a TTree instance as first argument");
321 return nullptr;
322 }
323
324 std::string klName;
325 if (clName) {
326 const char *clNameString = PyUnicode_AsUTF8AndSize(clName, nullptr);
327 if (!clNameString) {
328 return nullptr;
329 }
330 klName = clNameString;
331 }
332 void *buf = nullptr;
333
334 if (CPyCppyy::Instance_Check(address)) {
335 if (((CPPInstance *)address)->fFlags & CPPInstance::kIsReference)
336 buf = (void *)((CPPInstance *)address)->fObject;
337 else
338 buf = (void *)&((CPPInstance *)address)->fObject;
339
340 if (!clName) {
341 klName = GetTClass(address)->GetName();
342 argc += 1;
343 }
344 } else {
345 PyROOT::GetBuffer(address, buf);
346 }
347
348 if (buf && !klName.empty()) {
349 TBranch *branch = nullptr;
350 const char *nameString = nullptr;
351 if (argc == 4 || argc == 5 || argc == 6) {
352 nameString = PyUnicode_AsUTF8AndSize(name, nullptr);
353 if (!nameString) {
354 return nullptr;
355 }
356 }
357 if (argc == 4) {
358 branch = tree->Branch(nameString, klName.c_str(), buf);
359 } else if (argc == 5) {
360 branch = tree->Branch(nameString, klName.c_str(), buf, PyLong_AsLong(bufsize));
361 } else if (argc == 6) {
362 branch = tree->Branch(nameString, klName.c_str(), buf, PyLong_AsLong(bufsize),
363 PyLong_AsLong(splitlevel));
364 }
365
366 return BindCppObject(branch, Cppyy::GetScope("TBranch"));
367 }
368 }
369
371}
372
373////////////////////////////////////////////////////////////////////////////
374/// \brief Add pythonization for TTree::Branch.
375/// \param[in] self Always null, since this is a module function.
376/// \param[in] args Pointer to a Python tuple object containing the arguments
377/// received from Python.
378///
379/// Modify the behaviour of Branch so that proxy references can be passed
380/// as arguments from the Python side, more precisely in cases where the C++
381/// implementation of the method expects the address of a pointer.
382///
383/// For example:
384/// ~~~{.py}
385/// v = ROOT.std.vector('int')()
386/// t.Branch('my_vector_branch', v)
387/// ~~~
388///
389/// The following signatures are treated in this pythonization:
390/// - ( const char*, void*, const char*, Int_t = 32000 )
391/// - ( const char*, const char*, T**, Int_t = 32000, Int_t = 99 )
392/// - ( const char*, T**, Int_t = 32000, Int_t = 99 )
394{
395 int argc = PyTuple_Size(args);
396
397 if (argc >= 3) { // We count the TTree proxy object too
398 auto branch = TryBranchLeafListOverload(argc, args);
399 if (branch != Py_None)
400 return branch;
401
402 branch = TryBranchPtrToPtrOverloads(argc, args);
403 if (branch != Py_None)
404 return branch;
405 }
406
407 // Not the overload we wanted to pythonize, return None
409}
int Py_ssize_t
Definition CPyCppyy.h:215
#define Py_RETURN_NONE
Definition CPyCppyy.h:268
_object PyObject
long Long_t
Signed long integer 4 bytes (long). Size depends on architecture.
Definition RtypesCore.h:68
char name[80]
Definition TGX11.cxx:148
static TBranch * SearchForBranch(TTree *tree, const char *name)
Definition TTreePyz.cxx:61
PyObject * TryBranchLeafListOverload(int argc, PyObject *args)
Try to match the arguments of TTree::Branch to the following overload:
Definition TTreePyz.cxx:247
PyObject * TryBranchPtrToPtrOverloads(int argc, PyObject *args)
Try to match the arguments of TTree::Branch to one of the following overloads:
Definition TTreePyz.cxx:298
static std::pair< void *, std::string > ResolveBranch(TTree *tree, const char *name, TBranch *branch)
Definition TTreePyz.cxx:87
static TLeaf * SearchForLeaf(TTree *tree, const char *name, TBranch *branch)
Definition TTreePyz.cxx:71
static std::vector< dim_t > getMultiDims(std::string const &title)
Extracts static dimensions from the title of a TLeaf object.
Definition TTreePyz.cxx:124
static PyObject * WrapLeaf(TLeaf *leaf)
Definition TTreePyz.cxx:141
virtual PyObject * FromMemory(void *address)
A Branch for the case of an object.
static TClass * Class()
Int_t GetID() const
TStreamerInfo * GetInfo() const
Get streamer info for the branch class.
TClass * GetCurrentClass()
Return a pointer to the current type of the data member corresponding to branch element.
virtual TClass * GetTargetClass()
char * GetObject() const
Return a pointer to our object.
static TClass * Class()
A TTree is a list of TBranches.
Definition TBranch.h:93
virtual TLeaf * GetLeaf(const char *name) const
Return pointer to the 1st Leaf named name in thisBranch.
Definition TBranch.cxx:2054
virtual const char * GetClassName() const
Return the name of the user class whose content is stored in this branch, if any.
Definition TBranch.cxx:1323
virtual char * GetAddress() const
Definition TBranch.h:221
TClass * IsA() const override
Definition TBranch.h:304
TObjArray * GetListOfLeaves()
Definition TBranch.h:256
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
Definition TClass.cxx:2994
virtual Int_t GetSize() const
Return the capacity of the collection, i.e.
static TClass * Class()
static TClass * Class()
A TLeaf describes individual elements of a TBranch See TBranch structure in TTree.
Definition TLeaf.h:57
virtual void * GetValuePointer() const
Definition TLeaf.h:141
virtual const char * GetTypeName() const
Definition TLeaf.h:142
virtual TLeaf * GetLeafCount() const
If this leaf stores a variable-sized array or a multi-dimensional array whose last dimension has vari...
Definition TLeaf.h:124
TClass * IsA() const override
Definition TLeaf.h:171
virtual Int_t GetNdata() const
Definition TLeaf.h:139
TBranch * GetBranch() const
Definition TLeaf.h:119
virtual Int_t GetLenStatic() const
Return the fixed length of this leaf.
Definition TLeaf.h:135
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
const char * GetTitle() const override
Returns title of object.
Definition TNamed.h:50
An array of TObjects.
Definition TObjArray.h:31
TObject * Last() const override
Return the object in the last filled slot. Returns 0 if no entries.
TObject * At(Int_t idx) const override
Definition TObjArray.h:170
TObject * First() const override
Return the object in the first slot.
virtual Bool_t InheritsFrom(const char *classname) const
Returns kTRUE if object inherits from class "classname".
Definition TObject.cxx:549
Describe one element (data member) to be Streamed.
TObjArray * GetElements() const override
A TTree represents a columnar dataset.
Definition TTree.h:89
virtual TBranch * GetBranch(const char *name)
Return pointer to the branch with the given name in this tree or its friends.
Definition TTree.cxx:5430
virtual TLeaf * GetLeaf(const char *branchname, const char *leafname)
Return pointer to the 1st Leaf named name in any Branch of this Tree or any branch in the list of fri...
Definition TTree.cxx:6306
static TClass * Class()
Py_ssize_t dim_t
Definition API.h:89
CPYCPPYY_EXTERN bool Instance_Check(PyObject *pyobject)
Definition API.cxx:178
CPYCPPYY_EXTERN Converter * CreateConverter(const std::string &name, cdims_t=0)
PyObject * BindCppObject(Cppyy::TCppObject_t object, Cppyy::TCppType_t klass, const unsigned flags=0)
CPYCPPYY_EXTERN std::string Instance_GetScopedFinalName(PyObject *pyobject)
Definition API.cxx:106
CPYCPPYY_EXTERN void * Instance_AsVoidPtr(PyObject *pyobject)
Definition API.cxx:118
CPYCPPYY_EXTERN void DestroyConverter(Converter *p)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
PyObject * BranchPyz(PyObject *self, PyObject *args)
Add pythonization for TTree::Branch.
Definition TTreePyz.cxx:393
void GetBuffer(PyObject *pyobject, void *&buf)
PyObject * GetBranchAttr(PyObject *self, PyObject *args)
Definition TTreePyz.cxx:187