How to validated a boost::python::object is a function signature with an argument
Question: How to validated a boost::python::object argument is a python function
signature with an argument?

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


Answer: Boost.Python does not provide a higher-level type to help perform
introspection. However, one can use the Python C-API's
[`PyCallable_Check()`](https://docs.python.org/2/c-api/object.html#c.PyCallable_Check)
to check if a Python object is callable, and then use a Python introspection
module, such as [`inspect`](https://docs.python.org/2/library/inspect.html),
to determine the callable object's signature. Boost.Python's interoperability
between C++ and the Python makes this fairly seamless to use Python modules.

Here is an auxiliary function, `require_arity(fn, n)` that requires the
expression `fn(a_0, a_1, ... a_n)` to be valid:

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

And its usage would be:

    
    
    void subscribe_py(boost::python::object callback)
    {
      require_arity("callback", callback, 1); // callback(a1) is valid
      ...      
    }
    

* * *

Here is a complete example [demonstrating](http://coliru.stacked-
crooked.com/a/f8d5f23154e3e745) the usage:

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

Interactive usage:

    
    
    >>> 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)
    

Strict checking of function types can be argued as being non-Pythonic and can
become complicated due to the various types of callables (bound-method,
unbound method, classmethod, function, etc). Before applying strict type
checking, it may be worth assessing if strict type checking is required, or if
alternatives checks, such as [Abstract Base
Classes](https://www.python.org/dev/peps/pep-3119/), would be sufficient. For
instance, if the `callback` functor will be invoked within a Python thread,
then it may be worth not performing type checking, and allow the Python
exception to be raised when invoked. On the other hand, if the `callback`
functor will be invoked from within a non-Python thread, then type checking
within the initiating function can allow one to throw an exception within the
calling Python thread.

