Skip to content

Commit 0cc7d61

Browse files
committed
[PyROOT] Implement TTree SetBranchAddress pythonization in Python
The pythonization of `TTree::SetBranchAddress` was implemented in C++, hacking into CPyCppy by using implementation details like data member caches (this call: `((CPPInstance *)address)GetDatamemberCache()`). Not too surprising that it apparently breaks with the upcoming Python 3.13. It's better to implement the pythonizations in Python and also manage the lifetime of the necessary data in Python. This is done in this commit. The pythonization is extensively tested in `ttree_setbranchaddress.py`. Possible closes #15799.
1 parent 6952dd5 commit 0cc7d61

File tree

4 files changed

+59
-74
lines changed

4 files changed

+59
-74
lines changed

bindings/pyroot/pythonizations/python/ROOT/_pythonization/_ttree.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
*/
131131
'''
132132

133-
from libROOTPythonizations import GetBranchAttr, SetBranchAddressPyz, BranchPyz
133+
from libROOTPythonizations import GetBranchAttr, BranchPyz
134134
from . import pythonization
135135

136136
# TTree iterator
@@ -145,15 +145,66 @@ def _TTree__iter__(self):
145145
if bytes_read == -1:
146146
raise RuntimeError("TTree I/O error")
147147

148-
def _SetBranchAddress(self, *args):
149-
# Modify the behaviour if args is (const char*, void*)
150-
res = SetBranchAddressPyz(self, *args)
148+
def _pythonize_branch_addr(branch, addr_orig):
149+
"""Helper for the SetBranchAddress pythonization, extracting the relevant
150+
address from a Python object if possible.
151+
"""
152+
import cppyy
153+
import ctypes
151154

152-
if res is None:
153-
# Fall back to the original implementation for the rest of overloads
154-
res = self._OriginalSetBranchAddress(*args)
155+
# Pythonization for cppyy proxies (of type CPPInstance)
156+
if isinstance(addr_orig, cppyy._backend.CPPInstance):
155157

156-
return res
158+
is_leaf_list = branch.IsA() is cppyy.gbl.TBranch.Class()
159+
160+
if is_leaf_list:
161+
# If the branch is a leaf list, SetBranchAddress expects the
162+
# address of the object that has the corresponding data members.
163+
return ctypes.c_void_p(cppyy.addressof(instance=addr_orig, byref=False))
164+
165+
# Otherwise, SetBranchAddress is expecting a pointer to the address of
166+
# the object, and the pointer needs to stay alive. Therefore, we create
167+
# a container for the pointer and cache it in the original cppyy proxy.
168+
addr_view = cppyy.gbl.array["std::intptr_t", 1]([cppyy.addressof(instance=addr_orig, byref=False)])
169+
170+
if not hasattr(addr_orig, "_set_branch_cached_pointers"):
171+
addr_orig._set_branch_cached_pointers = []
172+
addr_orig._set_branch_cached_pointers.append(addr_view)
173+
174+
# Finally, we have to return the address of the container
175+
return ctypes.c_void_p(cppyy.addressof(instance=addr_view, byref=False))
176+
177+
# For NumPy arrays
178+
if hasattr(addr_orig, "__array_interface__"):
179+
return ctypes.c_void_p(addr_orig.__array_interface__["data"][0])
180+
181+
# For the builtin array library
182+
if hasattr(addr_orig, "buffer_info"):
183+
return ctypes.c_void_p(addr_orig.buffer_info()[0])
184+
185+
# We don't know how to pythonize the address parameter. return the
186+
# original value one.
187+
return addr_orig
188+
189+
def _SetBranchAddress(self, bname, addr, *args, **kwargs):
190+
"""
191+
Pythonization for TTree::SetBranchAddress.
192+
193+
Modify the behaviour of SetBranchAddress so that proxy references can be passed
194+
as arguments from the Python side, more precisely in cases where the C++
195+
implementation of the method expects the address of a pointer.
196+
197+
For example:
198+
```
199+
v = ROOT.std.vector('int')()
200+
t.SetBranchAddress("my_vector_branch", v)
201+
```
202+
"""
203+
204+
branch = self.GetBranch(bname)
205+
addr = _pythonize_branch_addr(branch, addr)
206+
207+
return self._OriginalSetBranchAddress(bname, addr, *args, **kwargs)
157208

158209
def _Branch(self, *args):
159210
# Modify the behaviour if args is one of:

bindings/pyroot/pythonizations/src/PyROOTModule.cxx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ static PyMethodDef gPyROOTMethods[] = {
4848
(char *)"Cast the void* returned by TClass::DynamicCast to the right type"},
4949
{(char *)"AddTObjectEqNePyz", (PyCFunction)PyROOT::AddTObjectEqNePyz, METH_VARARGS,
5050
(char *)"Add equality and inequality comparison operators to TObject"},
51-
{(char *)"SetBranchAddressPyz", (PyCFunction)PyROOT::SetBranchAddressPyz, METH_VARARGS,
52-
(char *)"Fully enable the use of TTree::SetBranchAddress from Python"},
5351
{(char *)"BranchPyz", (PyCFunction)PyROOT::BranchPyz, METH_VARARGS,
5452
(char *)"Fully enable the use of TTree::Branch from Python"},
5553
{(char *)"AddPrettyPrintingPyz", (PyCFunction)PyROOT::AddPrettyPrintingPyz, METH_VARARGS,

bindings/pyroot/pythonizations/src/PyROOTPythonize.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ PyObject *AddPrettyPrintingPyz(PyObject *self, PyObject *args);
2222

2323
PyObject *GetBranchAttr(PyObject *self, PyObject *args);
2424
PyObject *BranchPyz(PyObject *self, PyObject *args);
25-
PyObject *SetBranchAddressPyz(PyObject *self, PyObject *args);
2625

2726
PyObject *AddTClassDynamicCastPyz(PyObject *self, PyObject *args);
2827

bindings/pyroot/pythonizations/src/TTreePyz.cxx

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -201,69 +201,6 @@ PyObject *PyROOT::GetBranchAttr(PyObject * /*self*/, PyObject *args)
201201
return 0;
202202
}
203203

204-
////////////////////////////////////////////////////////////////////////////
205-
/// \brief Add pythonization for TTree::SetBranchAddress.
206-
/// \param[in] self Always null, since this is a module function.
207-
/// \param[in] args Pointer to a Python tuple object containing the arguments
208-
/// received from Python.
209-
///
210-
/// Modify the behaviour of SetBranchAddress so that proxy references can be passed
211-
/// as arguments from the Python side, more precisely in cases where the C++
212-
/// implementation of the method expects the address of a pointer.
213-
///
214-
/// For example:
215-
/// ~~~{.py}
216-
/// v = ROOT.std.vector('int')()
217-
/// t.SetBranchAddress("my_vector_branch", v)
218-
/// ~~~
219-
PyObject *PyROOT::SetBranchAddressPyz(PyObject * /* self */, PyObject *args)
220-
{
221-
PyObject *treeObj = nullptr, *name = nullptr, *address = nullptr;
222-
223-
int argc = PyTuple_GET_SIZE(args);
224-
225-
// Look for the (const char*, void*) overload
226-
auto argParseStr = "OUO:SetBranchAddress";
227-
if (argc == 3 && PyArg_ParseTuple(args, argParseStr, &treeObj, &name, &address)) {
228-
229-
auto tree = (TTree *)GetTClass(treeObj)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(treeObj));
230-
231-
if (!tree) {
232-
PyErr_SetString(PyExc_TypeError,
233-
"TTree::SetBranchAddress must be called with a TTree instance as first argument");
234-
return nullptr;
235-
}
236-
237-
auto branchName = PyUnicode_AsUTF8(name);
238-
auto branch = tree->GetBranch(branchName);
239-
if (!branch) {
240-
PyErr_SetString(PyExc_TypeError, "TTree::SetBranchAddress must be called with a valid branch name");
241-
return nullptr;
242-
}
243-
244-
bool isLeafList = branch->IsA() == TBranch::Class();
245-
246-
void *buf = 0;
247-
if (CPyCppyy::Instance_Check(address)) {
248-
((CPPInstance *)address)->GetDatamemberCache(); // force creation of cache
249-
250-
if (((CPPInstance *)address)->fFlags & CPPInstance::kIsReference || isLeafList)
251-
buf = CPyCppyy::Instance_AsVoidPtr(address);
252-
else
253-
buf = (void *)&(((CPPInstance *)address)->GetObjectRaw());
254-
} else
255-
Utility::GetBuffer(address, '*', 1, buf, false);
256-
257-
if (buf != nullptr) {
258-
auto res = tree->SetBranchAddress(PyUnicode_AsUTF8(name), buf);
259-
return PyInt_FromLong(res);
260-
}
261-
}
262-
263-
// Not the overload we wanted to pythonize, return None
264-
Py_RETURN_NONE;
265-
}
266-
267204
////////////////////////////////////////////////////////////////////////////
268205
/// Try to match the arguments of TTree::Branch to the following overload:
269206
/// - ( const char*, void*, const char*, Int_t = 32000 )

0 commit comments

Comments
 (0)