如何验证boost :: python :: object是一个带参数的函数签名

时间:2016-03-21 10:19:58

标签: boost-python

如何验证boost :: python :: object参数是一个带参数的python函数签名?

void subscribe_py(boost::python::object callback){
        //check callback is a function signature


    }

1 个答案:

答案 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线程中抛出异常。