Il concetto ResultConverter è progettato per risolvere questo problema. Il modello CallPolicy return_value_policy
utilizza un ResultConverterGenerator per creare un ResultConverter e il ResultConverter viene utilizzato per modificare il valore restituito di una funzione esposta a Python. In questo caso, è possibile utilizzare un tipo personalizzato che implementa il concetto ResultConverter per restituire Python None
o creare un'istanza di un oggetto con la classe Python appropriata. Mentre la lista di documentazione tutti i requisiti di tipo, può essere più facile da comprendere con qualcosa di più vicino che assomiglia codice:
/// @brief The ResultConverterGenerator.
struct result_converter_generator
{
template <typename T>
struct apply
{
struct result_converter
{
// Must be default constructible.
result_converter();
// Return true if T can be converted to a Python Object.
bool convertible();
// Convert obj to a PyObject, explicitly managing proper reference
// counts.
PyObject* operator(const T& obj);
// Returns the Python object that represents the type. Used for
// documentation.
const PyTypeObject* get_pytype();
};
/// @brief The ResultConverter.
typedef result_converter type;
};
};
Spesso, quando si crea un modello di ResultConverter personalizzato, si può utilizzare la maschera di meta-programmazione per ridurre al minimo le probabilità di errori di runtime nelle conversioni e persino rilevare errori in fase di compilazione e fornire messaggi significativi. Ecco un esempio completo di return_optional
, un modello ResultConverter che accetta un oggetto C++ boost::optional<T>
e lo converte nell'oggetto Python appropriato.
#include <boost/mpl/if.hpp>
#include <boost/optional.hpp>
#include <boost/python.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/utility/in_place_factory.hpp>
/// @brief Mockup model.
class spam {};
/// @brief Mockup factory for model.
boost::optional<spam> make_spam(bool x)
{
return x ? boost::optional<spam>(boost::in_place()) : boost::none;
}
namespace detail {
/// @brief Type trait that determines if the provided type is
/// a boost::optional.
template <typename T>
struct is_optional : boost::false_type {};
template <typename T>
struct is_optional<boost::optional<T> > : boost::true_type {};
/// @brief Type used to provide meaningful compiler errors.
template <typename>
struct return_optional_requires_a_optional_return_type {};
/// @brief ResultConverter model that converts a boost::optional object to
/// Python None if the object is empty (i.e. boost::none) or defers
/// to Boost.Python to convert object to a Python object.
template <typename T>
struct to_python_optional
{
/// @brief Only supports converting Boost.Optional types.
/// @note This is checked at runtime.
bool convertible() const { return detail::is_optional<T>::value; }
/// @brief Convert boost::optional object to Python None or a
/// Boost.Python object.
PyObject* operator()(const T& obj) const
{
namespace python = boost::python;
python::object result =
obj // If boost::optional has a value, then
? python::object(*obj) // defer to Boost.Python converter.
: python::object(); // Otherwise, return Python None.
// The python::object contains a handle which functions as
// smart-pointer to the underlying PyObject. As it will go
// out of scope, explicitly increment the PyObject's reference
// count, as the caller expects a non-borrowed (i.e. owned) reference.
return python::incref(result.ptr());
}
/// @brief Used for documentation.
const PyTypeObject* get_pytype() const { return 0; }
};
} // namespace detail
/// @brief Converts a boost::optional to Python None if the object is
/// equal to boost::none. Otherwise, defers to the registered
/// type converter to returs a Boost.Python object.
struct return_optional
{
template <class T> struct apply
{
// The to_python_optional ResultConverter only checks if T is convertible
// at runtime. However, the following MPL branch cause a compile time
// error if T is not a boost::optional by providing a type that is not a
// ResultConverter model.
typedef typename boost::mpl::if_<
detail::is_optional<T>,
detail::to_python_optional<T>,
detail::return_optional_requires_a_optional_return_type<T>
>::type type;
}; // apply
}; // return_optional
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<spam>("Spam")
;
python::def("make_spam", &make_spam,
python::return_value_policy<return_optional>());
}
utilizzo interattivo:
>>> import example
>>> assert(isinstance(example.make_spam(True), example.Spam))
>>> assert(example.make_spam(False) is None)
Il tempo di compilazione controllo di tipo si verifica quando il return_optional
ResultConvert si cerca di essere utilizzato con una funzione che restituisce un valore che non è un boost::optional
. Per esempio, viene utilizzato quando il seguente:
struct egg {};
egg* make_egg();
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::def("make_egg", &make_egg,
python::return_value_policy<return_optional>());
}
Il compilatore selezionerà selezionare l'attuazione return_optional_requires_a_optional_return_type
, e non riescono compilazione. Qui è parte del messaggio di errore del compilatore clang fornisce:
error: no member named 'get_pytype' in
'detail::return_optional_requires_a_optional_return_type<egg *>'
Questo dovrebbe davvero essere parte della libreria boost :: python. Veramente bello! – Felix
Questa è una risposta davvero fenomenale. – Mohan