Skip to content

Commit 6ddd17c

Browse files
committed
[pyroot] Refactor RDF Filter/Define callable dispatch [ROOT-patch]
1 parent a7f28f0 commit 6ddd17c

1 file changed

Lines changed: 26 additions & 127 deletions

File tree

  • bindings/pyroot/pythonizations/python/ROOT/_pythonization

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

Lines changed: 26 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -231,139 +231,30 @@ def jit_function(self, func, cols_list, extra_args):
231231
return self.func_call
232232

233233

234-
def remove_fn_name_from_signature(signature):
235-
"""
236-
Removes the function name from a signature string.
237-
The signature is expected to be in the form of "return_type function_name(type param1, type param2, ...)".
238-
"""
239-
if "(" not in signature or ")" not in signature:
240-
raise ValueError(f"Invalid signature format: {signature}")
241-
242-
return signature[: signature.index(" ")] + signature[signature.index("(") :]
243-
244-
245-
def get_cpp_overload_from_templ_proxy(func, types=None):
246-
"""
247-
Gets the C++ overload from a cppyy TemplateProxy using the input
248-
and template arguments types.
249-
"""
250-
signature = ", ".join(types) if types else ""
251-
template_args = func.__template_args__[1:-1] if func.__template_args__ else ""
252-
return func.__overload__(signature, template_args)
253-
254-
255234
def get_column_types(rdf, cols):
256235
return [rdf.GetColumnType(col) for col in cols]
257236

258237

259-
def get_overload_based_on_args(overload_types, types):
260-
"""
261-
Gets the C++ overloads for a function based on the provided signatures and types.
262-
"""
263-
if not isinstance(overload_types, dict):
264-
raise TypeError("Overload types must be a dictionary.")
265-
266-
if not isinstance(types, list) or not all(isinstance(t, str) for t in types):
267-
raise TypeError("Types must be a list of strings.")
268-
269-
if len(overload_types) == 1:
270-
# If there is only one signature, return it directly
271-
return next(iter(overload_types))
272-
273-
for full_sig, info in overload_types.items():
274-
input_types = info.get("input_types", ())
275-
276-
if len(input_types) != len(types):
277-
continue
278-
279-
match = True
280-
for expected, actual in zip(input_types, types):
281-
# Loose match: require actual to appear somewhere in expected (e.g. "int" in "const int&")
282-
if actual not in expected:
283-
match = False
284-
break
285-
286-
if match:
287-
return full_sig
288-
289-
raise ValueError(
290-
f"No matching overload found for types: {types} in signatures: {overload_types.keys()}. Please check the function overloads."
291-
)
292-
293-
294-
def _get_cpp_signature(func, rdf, cols):
238+
def _resolve_overload_based_on_cols(func, rdf, cols):
295239
"""
296240
Gets the C++ signature of a cppyy callable.
297241
"""
298242
import ROOT
299243

300-
if isinstance(func, ROOT._cppyy.types.Template):
301-
func = get_cpp_overload_from_templ_proxy(func, get_column_types(rdf, cols))
302-
303-
if not isinstance(func, ROOT._cppyy.types.Function):
244+
if not isinstance(func, (ROOT._cppyy.types.Function, ROOT._cppyy.types.Template)):
304245
raise TypeError(f"Expected a cppyy callable, got {type(func).__name__}")
305246

306-
overload_types = func.func_overloads_types
307-
matched_overload = get_overload_based_on_args(overload_types, get_column_types(rdf, cols))
308-
return remove_fn_name_from_signature(matched_overload)
247+
if isinstance(func, ROOT._cppyy.types.Template) and func.__template_args__:
248+
template_args = func.__template_args__[1:-1]
249+
else:
250+
template_args = ""
309251

310-
311-
def _to_std_function(func, rdf, cols):
312-
"""
313-
Converts a cppyy callable to std::function.
314-
"""
315-
import ROOT
316-
317-
if not isinstance(func, ROOT._cppyy.types.Function) and not isinstance(func, ROOT._cppyy.types.Template):
318-
raise TypeError(f"Expected a cppyy callable, got {type(func).__name__}")
319-
320-
signature = _get_cpp_signature(func, rdf, cols)
321-
return ROOT.std.function(signature)
322-
323-
324-
def _handle_cpp_callables(func, original_template, *args, rdf=None, cols=None):
325-
"""
326-
Checks whether the callable `func` is a cppyy proxy of one of these:
327-
1. C++ functor
328-
2. std::function
329-
3. C++ free function
330-
331-
Cases 1 and 2 above are supported by cppyy, so we can just invoke the original
332-
cppyy TemplateProxy (Filter or Define) with the callable as argument.
333-
For case 3, we need to convert the callable to a std::function
334-
before invoking the original cppyy TemplateProxy.
335-
336-
Prior to the invocation of the original cppyy TemplateProxy, though, we
337-
need to explicitly instantiate the template using the type of the `func`
338-
callable. Implicit instantiation won't work since the original
339-
implementation of Filter and Define is replaced by a pythonization.
340-
341-
Arguments:
342-
func: callable to be checked
343-
original_rdf_method: cppyy proxy for Filter or Define
344-
args: arguments passed by the user to Filter or Define
345-
Returns:
346-
RDataFrame: RDataFrame node if `func` can be classified in one of the
347-
three cases above, None otherwise
348-
"""
349-
import ROOT
350-
351-
is_cpp_functor = lambda: isinstance(getattr(func, "__call__", None), ROOT._cppyy.types.Function) # noqa E731
352-
353-
is_std_function = lambda: isinstance(getattr(func, "target_type", None), ROOT._cppyy.types.Function) # noqa E731
354-
355-
# handle free functions
356-
if callable(func) and not is_cpp_functor() and not is_std_function():
357-
try:
358-
func = _to_std_function(func, rdf, cols)
359-
except TypeError as e:
360-
if "Expected a cppyy callable" in str(e):
361-
pass # this function is not convertible to std::function, move on to the next check
362-
else:
363-
raise
364-
365-
if is_cpp_functor() or is_std_function():
366-
return original_template[type(func)](*args)
252+
col_types = get_column_types(rdf, cols)
253+
signature = ", ".join(col_types) if cols else ""
254+
if template_args:
255+
return func.__overload__(signature, template_args)
256+
else:
257+
return func.__overload__(signature)
367258

368259

369260
class _WarnOnce:
@@ -460,9 +351,13 @@ def x_more_than_y(x):
460351
f"Arguments should be ('list', 'str',) not ({type(args[0]).__name__, type(args[1]).__name__}."
461352
)
462353

463-
rdf_node = _handle_cpp_callables(func, rdf._OriginalFilter, func, *args, rdf=rdf, cols=col_list)
464-
if rdf_node is not None:
465-
return rdf_node
354+
import ROOT
355+
356+
if isinstance(func, (ROOT._cppyy.types.Function, ROOT._cppyy.types.Template)):
357+
return rdf._OriginalFilter(_resolve_overload_based_on_cols(func, rdf, col_list), *args)
358+
359+
if isinstance(func, ROOT._cppyy.types.Instance):
360+
return rdf._OriginalFilter(func, *args)
466361

467362
_WarnOnce.warn()
468363
jitter = FunctionJitter(rdf)
@@ -520,9 +415,13 @@ def x_scaled(x):
520415
raise TypeError(f"Define takes a column list as third arguments but {type(cols).__name__} was given.")
521416

522417
func = callable_or_str
523-
rdf_node = _handle_cpp_callables(func, rdf._OriginalDefine, col_name, func, cols, rdf=rdf, cols=cols)
524-
if rdf_node is not None:
525-
return rdf_node
418+
419+
import ROOT
420+
421+
if isinstance(func, (ROOT._cppyy.types.Function, ROOT._cppyy.types.Template)):
422+
return rdf._OriginalDefine(col_name, _resolve_overload_based_on_cols(func, rdf, cols), cols)
423+
if isinstance(func, ROOT._cppyy.types.Instance):
424+
return rdf._OriginalDefine(col_name, func, cols)
526425

527426
_WarnOnce.warn()
528427
jitter = FunctionJitter(rdf)

0 commit comments

Comments
 (0)