如何驗證boost :: python :: object參數是一個帶參數的python函數簽名?如何驗證boost :: python :: object是帶參數的函數簽名
void subscribe_py(boost::python::object callback){
//check callback is a function signature
}
如何驗證boost :: python :: object參數是一個帶參數的python函數簽名?如何驗證boost :: python :: object是帶參數的函數簽名
void subscribe_py(boost::python::object callback){
//check callback is a function signature
}
Boost.Python的不提供更高級別的類型,幫助進行反省。但是,可以使用Python C-API的PyCallable_Check()
來檢查Python對象是否可調用,然後使用Python自檢模塊(例如inspect
)來確定可調用對象的簽名。 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)
函數類型的嚴格的檢查可以認爲作爲非Python化和可能會變得複雜,由於各種類型的可調用(結合方法中,未結合的方法,類方法,功能等的)。在應用嚴格類型檢查之前,可能需要評估是否需要進行嚴格類型檢查,或者是否需要其他檢查(如Abstract Base Classes)就足夠了。例如,如果將在Python線程中調用callback
仿函數,那麼可能不值得執行類型檢查,並允許在調用時引發Python異常。另一方面,如果將從非Python線程中調用callback
仿函數,那麼在啓動函數內鍵入檢查可以允許在調用Python線程中引發異常。