2016-03-21 10 views

答えて

1

Boost.Pythonは、イントロスペクションを実行するのに役立つために、より高いレベルの種類を提供していません。しかし、Python C-APIのPyCallable_Check()を使用して、Pythonオブジェクトが呼び出し可能かどうかを確認してから、inspectなどのPythonイントロスペクションモジュールを使用して、呼び出し可能オブジェクトの署名を判断することができます。 Boost.PythonのC++とPythonの相互運用性により、これはPythonモジュールを使用するためにかなりシームレスになります。ここで

は有効であると表現 fn(a_0, a_1, ... a_n)を必要とする補助機能、 require_arity(fn, 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のような代替検査で十分であるかどうかを判断する価値があります。たとえば、callbackというファンクタがPythonスレッド内で呼び出された場合、型チェックを実行しないで、呼び出されたときにPython例外が発生するのを防ぐことができます。一方、callbackファンクタが非Pythonスレッド内から呼び出された場合、起動関数内で型チェックを行うと、呼び出し側のPythonスレッド内で例外をスローすることができます。

関連する問題