如何验证 boost::python::object 是带有参数的函数签名
How to validated a boost::python::object is a function signature with an argument
如何验证 boost::python::object 参数是带有参数的 python 函数签名?
void subscribe_py(boost::python::object callback){
//check callback is a function signature
}
Boost.Python 不提供更高级别的类型来帮助执行内省。但是,可以使用 Python C-API 的 PyCallable_Check()
to check if a Python object is callable, and then use a Python introspection module, such as 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)
函数类型的严格检查可以说是非 Pythonic,并且由于可调用对象的各种类型(绑定方法、未绑定方法、类方法、函数等)而变得复杂。在应用严格类型检查之前,可能值得评估是否需要严格类型检查,或者替代检查(例如 Abstract Base Classes 是否足够)。例如,如果 callback
仿函数将在 Python 线程中调用,那么不执行类型检查可能是值得的,并允许在调用时引发 Python 异常。另一方面,如果将从非 Python 线程中调用 callback
仿函数,则启动函数中的类型检查可以允许在调用 Python 中抛出异常线程。
如何验证 boost::python::object 参数是带有参数的 python 函数签名?
void subscribe_py(boost::python::object callback){
//check callback is a function signature
}
Boost.Python 不提供更高级别的类型来帮助执行内省。但是,可以使用 Python C-API 的 PyCallable_Check()
to check if a Python object is callable, and then use a Python introspection module, such as 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)
函数类型的严格检查可以说是非 Pythonic,并且由于可调用对象的各种类型(绑定方法、未绑定方法、类方法、函数等)而变得复杂。在应用严格类型检查之前,可能值得评估是否需要严格类型检查,或者替代检查(例如 Abstract Base Classes 是否足够)。例如,如果 callback
仿函数将在 Python 线程中调用,那么不执行类型检查可能是值得的,并允许在调用时引发 Python 异常。另一方面,如果将从非 Python 线程中调用 callback
仿函数,则启动函数中的类型检查可以允许在调用 Python 中抛出异常线程。