From d50fbcf36e5a741ac063c73e2779d9367e15676d Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Tue, 12 May 2026 12:36:35 +0200 Subject: [PATCH] [Python] Re-implement `TTree.SetBranchAddress` template call in Python Previously, when the data type of the address argument could be determined (e.g. for NumPy arrays or `array.array` objects), we relied on cppyy to pick the right template overload of `SetBranchAddress` by indexing with the data type. However, TTree::SetBranchAddress has two template overloads: ```c++ template int SetBranchAddress(const char *bname, T **add, ...); template int SetBranchAddress(const char *bname, T *add, ...); ``` Python lacks pointer semantics, so cppyy cannot meaningfully disambiguate between these two candidates and considers both valid. Until now, we happened to be lucky that cppyy tried the `T *` overload first, which is the one we need. As cppyy becomes stricter about overload resolution ambiguity, this will start failing. To avoid relying on this accident, replicate on the Python side what the template overload does internally: look up the `TClass` for the given type, fall back to `TDataType::GetType` via `cppyy.typeid` otherwise, and then call the non-template `SetBranchAddress` overload directly with the resolved class and type arguments. --- .../python/ROOT/_pythonization/_ttree.py | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_ttree.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_ttree.py index 52be9dd512669..77cd655577c3a 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_ttree.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_ttree.py @@ -232,8 +232,7 @@ def _get_cpp_type_from_array_typecode(typecode): def _determine_data_type(addr): - """ Figure out data_type in case addr is a numpy.ndarray or array.array. - """ + """Figure out data_type in case addr is a numpy.ndarray or array.array.""" # For NumPy arrays if hasattr(addr, "__array_interface__"): @@ -260,6 +259,8 @@ def _SetBranchAddress(self, bname, addr, *args, **kwargs): t.SetBranchAddress("my_vector_branch", v) ``` """ + import cppyy + import ROOT branch = self.GetBranch(bname) @@ -271,10 +272,37 @@ def _SetBranchAddress(self, bname, addr, *args, **kwargs): # Figure out data_type in case addr is a numpy.ndarray or array.array data_type = _determine_data_type(addr) - # We call the template specialization if we know the data type - func = self._OriginalSetBranchAddress if data_type is None else self._OriginalSetBranchAddress[data_type] - - return func(bname, addr, *args, **kwargs) + if data_type is None: + return self._OriginalSetBranchAddress(bname, addr, *args, **kwargs) + + # In the case the data_type is available, we would like to call the + # template overload of SetBranchAddress instantiatied for that type. + # However, there are two such overloads candidates: + # + # template int TTree::SetBranchAddress(const char *bname, T **add, ...); + # template int TTree::SetBranchAddress(const char *bname, T *add, ...); + # + # The cppyy bindings can't make a meaningful selection here as Python is + # lacking pointer semantics, so it considers both overloads as valid + # choices. In the past, we just happened to be lucky that it tried the T * + # overload first, which is the one we need. But as cppyy becomes more + # strict about overload resolution ambiguity errors, this won't work + # anymore. That's why we re-implement what happens in the template overload + # on the Python side. + + cl = ROOT.TClass.GetClass[data_type]() + tp = ROOT.kOther_t + if not cl: + tp = ROOT.TDataType.GetType(cppyy.typeid(getattr(ROOT, data_type))) + + # Extract the TBranch ptr argument if available + tbranch_ptr = ROOT.nullptr + if len(args) > 0: + tbranch_ptr = args[0] + elif "ptr" in kwargs: + tbranch_ptr = kwargs["ptr"] + + return self._OriginalSetBranchAddress(bname, addr, ptr=tbranch_ptr, realClass=cl, datatype=tp, isptr=False) def _Branch(self, *args): @@ -314,6 +342,7 @@ def _TTree__getattr__(self, key): out = ROOT._cppyy.ll.cast[cast_type](out) return out + def _TTree_CloneTree(self, *args, **kwargs): """ Forward the arguments to the C++ function and give up ownership if the @@ -327,6 +356,7 @@ def _TTree_CloneTree(self, *args, **kwargs): return out_tree + @pythonization("TTree") def pythonize_ttree(klass, name): # Parameters: @@ -380,6 +410,7 @@ def pythonize_tchain(klass): klass._OriginalSetBranchAddress = klass.SetBranchAddress klass.SetBranchAddress = _SetBranchAddress + @pythonization("TNtuple") def pythonize_tntuple(klass):