2016-03-21 31 views

cevap

1

Boost.Python iç gözlem gerçekleştirmek yardımcı olmak için daha üst düzey bir türü sağlamaz. Bununla birlikte, bir Python nesnesinin callable olup olmadığını kontrol etmek için Python C-API'sı PyCallable_Check() kullanabilir ve sonra, çağrılabilir nesnesinin imzasını belirlemek için inspect gibi bir Python iç geçirme modülü kullanabilirsiniz. Boost.Python'un C++ ve Python arasındaki birlikte çalışabilirliği, Python modüllerini kullanmak için bu kadar sorunsuz bir hale getirir. İşte

geçerli olabilmesi için ifadesini fn(a_0, a_1, ... a_n) gerektiren bir yardımcı fonksiyon, require_arity(fn, n) geçerli:

/// @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(); 
} 
} 

Ve kullanım olacaktır: Burada


void subscribe_py(boost::python::object callback) 
{ 
    require_arity("callback", callback, 1); // callback(a1) is valid 
    ...  
} 
tam bir örnek demonstrating olduğunu kullanımı:

#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); 
} 

Etkileşimli kullanımı:

fonksiyon tiplerinin

Sıkı kontrol nedeniyle vb callables (bağlı-yöntemde, ilişkisiz yöntemle, classmethod, fonksiyonun, çeşitli olmanın olmayan Pythonic olarak tartışılabilir ve karmaşık bir hale bürünebilir

>>> 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) 
). Sıkı tip kontrolünü uygulamadan önce, sıkı tip kontrolünün gerekli olup olmadığını değerlendirmek veya Abstract Base Classes gibi alternatif kontroller yeterli olacaktır. Örneğin, callback functor bir Python iş parçacığı içinde çağrılacaksa, bu durumda tür denetimi gerçekleştirmeye değmez ve Python istisnasının çağrılırken yükseltilmesine izin verilebilir. Diğer taraftan, callback functor, Python olmayan bir iş parçacığı içinden çağrılacaksa, başlatma işlevindeki denetimi, çağrı yapan Python iş parçacığı içinde bir özel durum atamasına izin verebilir.

İlgili konular