Skip to content

Commit 23d73be

Browse files
authored
Fix callable detection to support functools.partial and all callable objects (#3121)
* Fix callable detection to support all callable objects (functools.partial, etc) Fixes #2008 The old implementation checked for __code__ attribute directly, which doesn't exist for callable objects like functools.partial. This caused an AttributeError when trying to use such objects with find_max_global and find_min_global functions. Changed to use Python's inspect.signature() which properly handles all callable objects including: - Regular functions - Lambda functions - functools.partial objects - Class methods - Any object with __call__ method The fix includes a fallback to the old __code__ method for backward compatibility in case inspect.signature fails for any reason. * Update the code to loop through params.values() instead of params itself, since parameters is a dictionary-like object. The earlier code tried to access .second on the iterator, which isn’t valid when working with dict values Signed-off-by: Samaresh Kumar Singh <[email protected]> * Fix parameter counting to exclude *args and **kwargs The previous code was counting VAR_POSITIONAL (*args) and VAR_KEYWORD (**kwargs) parameters in the total count, which caused functions like 'lambda a, b, c, *args' to be counted as having 4 parameters instead of 3. Now only regular parameters are counted, while still checking for the presence of *args to allow variable argument functions. Also removed test_callable_fix.py as it was causing pytest errors. Signed-off-by: Samaresh Kumar Singh <[email protected]> --------- Signed-off-by: Samaresh Kumar Singh <[email protected]>
1 parent 20b2172 commit 23d73be

File tree

1 file changed

+39
-5
lines changed

1 file changed

+39
-5
lines changed

tools/python/src/global_optimization.cpp

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,45 @@ py::list mat_to_list (
4848

4949
size_t num_function_arguments(py::object f, size_t expected_num)
5050
{
51-
const auto code_object = f.attr(hasattr(f,"func_code") ? "func_code" : "__code__");
52-
const auto num = code_object.attr("co_argcount").cast<std::size_t>();
53-
if (num < expected_num && (code_object.attr("co_flags").cast<int>() & CO_VARARGS))
54-
return expected_num;
55-
return num;
51+
// Use Python's inspect module to get signature, which works with all callable objects
52+
// including functools.partial, lambdas, and regular functions
53+
auto inspect = py::module_::import("inspect");
54+
55+
try {
56+
auto sig = inspect.attr("signature")(f);
57+
auto params = sig.attr("parameters");
58+
59+
// Count only regular parameters, excluding VAR_POSITIONAL (*args) and VAR_KEYWORD (**kwargs)
60+
size_t num_regular_params = 0;
61+
bool has_var_args = false;
62+
63+
for (auto item : params.attr("values")()) {
64+
auto param = item.cast<py::object>();
65+
auto kind = param.attr("kind").cast<int>();
66+
// inspect.Parameter.VAR_POSITIONAL == 2, VAR_KEYWORD == 4
67+
if (kind == 2) {
68+
has_var_args = true;
69+
} else if (kind != 4) {
70+
// Count all parameters except VAR_POSITIONAL and VAR_KEYWORD
71+
num_regular_params++;
72+
}
73+
}
74+
75+
if (num_regular_params < expected_num && has_var_args)
76+
return expected_num;
77+
return num_regular_params;
78+
} catch (const py::error_already_set&) {
79+
// Fallback to old method if inspect.signature fails
80+
// This maintains backward compatibility
81+
if (!hasattr(f, "__code__") && !hasattr(f, "func_code")) {
82+
throw;
83+
}
84+
const auto code_object = f.attr(hasattr(f,"func_code") ? "func_code" : "__code__");
85+
const auto num = code_object.attr("co_argcount").cast<std::size_t>();
86+
if (num < expected_num && (code_object.attr("co_flags").cast<int>() & CO_VARARGS))
87+
return expected_num;
88+
return num;
89+
}
5690
}
5791

5892
double call_func(py::object f, const matrix<double,0,1>& args)

0 commit comments

Comments
 (0)