如何验证boost :: python :: object参数是一个带参数的python函数签名?
void subscribe_py(boost::python::object callback){
//check callback is a function signature
}
答案 0 :(得分:1)
Boost.Python不提供更高级别的类型来帮助执行内省。但是,可以使用Python C-API的PyCallable_Check()
来检查Python对象是否可调用,然后使用Python内省模块(例如inspect
)来确定可调用对象&# 39;签名Boost.Python在C ++和Python之间的互操作性使得使用Python模块相当无缝。
这是一个辅助函数require_arity(fn, n)
,要求表达式fn(a_0, a_1, ... a_n)
有效:
/// @brief Given a Python object `fn` and an arity of `n`, requires
/// that the expression `fn(a_0, a_1, ..., a_2` to be valid.
/// Raise TypeError if `fn` is not callable and `ValueError`
/// if `fn` is callable, but has the wrong arity.
void require_arity(
std::string name,
boost::python::object fn,
std::size_t arity)
{
namespace python = boost::python;
std::stringstream error_msg;
error_msg << name << "() must take exactly " << arity << " arguments";
// Throw if the callback is not callable.
if (!PyCallable_Check(fn.ptr()))
{
PyErr_SetString(PyExc_TypeError, error_msg.str().c_str());
python::throw_error_already_set();
}
// Use the inspect module to extract the arg spec.
// >>> import inspect
auto inspect = python::import("inspect");
// >>> args, varargs, keywords, defaults = inspect.getargspec(fn)
auto arg_spec = inspect.attr("getargspec")(fn);
python::object args = arg_spec[0];
python::object varargs = arg_spec[1];
python::object defaults = arg_spec[3];
// Calculate the number of required arguments.
auto args_count = args ? python::len(args) : 0;
auto defaults_count = defaults ? python::len(defaults) : 0;
// If the function is a bound method or a class method, then the
// first argument (`self` or `cls`) will be implicitly provided.
// >>> has_self = inspect.ismethod(fn) and fn.__self__ is not None
if (static_cast<bool>(inspect.attr("ismethod")(fn))
&& fn.attr("__self__"))
{
--args_count;
}
// Require at least one argument. The function should support
// any of the following specs:
// >>> fn(a1)
// >>> fn(a1, a2=42)
// >>> fn(a1=42)
// >>> fn(*args)
auto required_count = args_count - defaults_count;
if (!( (required_count == 1) // fn(a1), fn(a1, a2=42)
|| (args_count > 0 && required_count == 0) // fn(a1=42)
|| (varargs) // fn(*args)
))
{
PyErr_SetString(PyExc_ValueError, error_msg.str().c_str());
python::throw_error_already_set();
}
}
它的用法是:
void subscribe_py(boost::python::object callback)
{
require_arity("callback", callback, 1); // callback(a1) is valid
...
}
以下是一个完整的示例demonstrating用法:
#include <boost/python.hpp>
#include <sstream>
/// @brief Given a Python object `fn` and an arity of `n`, requires
/// that the expression `fn(a_0, a_1, ..., a_2` to be valid.
/// Raise TypeError if `fn` is not callable and `ValueError`
/// if `fn` is callable, but has the wrong arity.
void require_arity(
std::string name,
boost::python::object fn,
std::size_t arity)
{
namespace python = boost::python;
std::stringstream error_msg;
error_msg << name << "() must take exactly " << arity << " arguments";
// Throw if the callback is not callable.
if (!PyCallable_Check(fn.ptr()))
{
PyErr_SetString(PyExc_TypeError, error_msg.str().c_str());
python::throw_error_already_set();
}
// Use the inspect module to extract the arg spec.
// >>> import inspect
auto inspect = python::import("inspect");
// >>> args, varargs, keywords, defaults = inspect.getargspec(fn)
auto arg_spec = inspect.attr("getargspec")(fn);
python::object args = arg_spec[0];
python::object varargs = arg_spec[1];
python::object defaults = arg_spec[3];
// Calculate the number of required arguments.
auto args_count = args ? python::len(args) : 0;
auto defaults_count = defaults ? python::len(defaults) : 0;
// If the function is a bound method or a class method, then the
// first argument (`self` or `cls`) will be implicitly provided.
// >>> has_self = inspect.ismethod(fn) and fn.__self__ is not None
if (static_cast<bool>(inspect.attr("ismethod")(fn))
&& fn.attr("__self__"))
{
--args_count;
}
// Require at least one argument. The function should support
// any of the following specs:
// >>> fn(a1)
// >>> fn(a1, a2=42)
// >>> fn(a1=42)
// >>> fn(*args)
auto required_count = args_count - defaults_count;
if (!( (required_count == 1) // fn(a1), fn(a1, a2=42)
|| (args_count > 0 && required_count == 0) // fn(a1=42)
|| (varargs) // fn(*args)
))
{
PyErr_SetString(PyExc_ValueError, error_msg.str().c_str());
python::throw_error_already_set();
}
}
void perform(
boost::python::object callback,
boost::python::object arg1)
{
require_arity("callback", callback, 1);
callback(arg1);
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::def("perform", &perform);
}
交互式使用:
>>> import example
>>> def test(fn, a1, expect=None):
... try:
... example.perform(fn, a1)
... assert(expect is None)
... except Exception as e:
... assert(isinstance(e, expect))
...
>>> test(lambda x: 42, None)
>>> test(lambda x, y=2: 42, None)
>>> test(lambda x=1, y=2: 42, None)
>>> test(lambda *args: None, None)
>>> test(lambda: 42, None, ValueError)
>>> test(lambda x, y: 42, None, ValueError)
>>>
>>> class Mock:
... def method_no_arg(self): pass
... def method_with_arg(self, x): pass
... def method_default_arg(self, x=1): pass
... @classmethod
... def cls_no_arg(cls): pass
... @classmethod
... def cls_with_arg(cls, x): pass
... @classmethod
... def cls_with_default_arg(cls, x=1): pass
...
>>> mock = Mock()
>>> test(Mock.method_no_arg, mock)
>>> test(mock.method_no_arg, mock, ValueError)
>>> test(Mock.method_with_arg, mock, ValueError)
>>> test(mock.method_with_arg, mock)
>>> test(Mock.method_default_arg, mock)
>>> test(mock.method_default_arg, mock)
>>> test(Mock.cls_no_arg, mock, ValueError)
>>> test(mock.cls_no_arg, mock, ValueError)
>>> test(Mock.cls_with_arg, mock)
>>> test(mock.cls_with_arg, mock)
>>> test(Mock.cls_with_default_arg, mock)
>>> test(mock.cls_with_default_arg, mock)
严格检查函数类型可以被认为是非Pythonic,并且由于各种类型的可调用(绑定方法,非绑定方法,类方法,函数等)而变得复杂。在应用严格类型检查之前,可能值得评估是否需要进行严格的类型检查,或者是否需要替代检查(例如Abstract Base Classes)。例如,如果在Python线程中调用callback
仿函数,则可能不值得执行类型检查,并允许在调用时引发Python异常。另一方面,如果将在非Python线程中调用callback
仿函数,则在启动函数中进行类型检查可以允许在调用Python线程中抛出异常。