2016-06-14 51 views
11

bağlamaları eklemek gibi bir şey yazmak zorunda:Boost.Python gerçekten çalışıyor bir şekilde Python için bir C++ özel durum açığa çıkarmak amacıyla (istisna işleme için) mevcut PyObject

std::string scope = py::extract<std::string>(py::scope().attr("__name__")); 
std::string full_name = scope + "." + name; 
PyObject* exc_type = PyErr_NewException(&full_name[0], PyExc_RuntimeError, 0); 
// ... 

Ama bu kötü kokan Boost.Python'da başka bir şeyle etkileşime girmiş gibi görünüyor. Ifşa etmek isterseniz:

struct Error { int code; }; 

Ben yazabilirsiniz:

py::class_<Error>("Error", py::no_init) 
    .def_readonly("code", &Error::code) 
; 

nasıl PyErr_NewException üzerinde istisna yaratılması ile Error için bağlayıcı sınıfını birleştirebilirsiniz? Temel olarak, throw Error{42}'u istiyorum ve bunu Python'dan açıkça görüyorum: Error veya RuntimeError tarafından yakalanabilir ve bu işe sahip olabilirim ve AssertionError (veya benzeri) tarafından yakalanabilir ve Error'u yakalayamaz veya SystemError .

+1

ama, [ 'Cython' bu işlemek gibi görünüyor güzel] (http://docs.cython.org/src/userguide/wrapping_CPlusPlus.html#exceptions) . Belki "C++" kodunun çoğunu "boost-python" ile kaplayabilir ve bu tür zorlu durumları Cython gibi daha esnek araçlarla halledebilirsiniz. –

cevap

5

class_ ile oluşturulan Python türünde Python exceptions türleri ile uyumsuz bir düzen vardır. Hem hiyerarşisinde hem de bir tür oluşturmayı denemek, TypeError ile başarısız olur. tür denetlemesi yapacak Python fıkra hariç, seçeneklerden biri yaratmaktır gibi bir Python istisnası yazın:

  • olan gömülü konu nesneye istenilen Python özel durum türü (ler) den
  • vekiller türetir

    • bir Python oluşturun: Boost.Python

    Bu yaklaşım sayesinde maruz kalan bir türün örneği birkaç adım gerektirir gömülü konusu nesneye temsil durumunda

  • konu olan bir nesneyi katıştırmak için kullanıcı tanımlı Python istisnanın başlatıcı yama böylece durum türü Python İstisnalar türeyen
  • kullanıcı tanımlı Python istisnanın __delattr__, __getattr__ ve __setattr yöntem değiştirme hangi o olacak vekil

şöyle yaklaşımın saf Python uygulaması olacaktır:

def as_exception(base): 
    ''' Decorator that will return a type derived from `base` and proxy to the 
     decorated class. 

    ''' 
    def make_exception_type(wrapped_cls): 
     # Generic proxying to subject. 
     def del_subject_attr(self, name): 
      return delattr(self._subject, name) 

     def get_subject_attr(self, name): 
      return getattr(self._subject, name) 

     def set_subject_attr(self, name, value): 
      return setattr(self._subject, name, value) 

     # Create new type that derives from base and proxies to subject. 
     exception_type = type(wrapped_cls.__name__, (base,), { 
      '__delattr__': del_subject_attr, 
      '__getattr__': get_subject_attr, 
      '__setattr__': set_subject_attr, 
     }) 

     # Monkey-patch the initializer now that it has been created. 
     original_init = exception_type.__init__ 

     def init(self, *args, **kwargs): 
      original_init(self, *args, **kwargs) 
      self.__dict__['_subject'] = wrapped_cls(*args, **kwargs) 
     exception_type.__init__ = init 

     return exception_type 
    return make_exception_type 


@as_exception(RuntimeError) 
class Error: 
    def __init__(self, code): 
     self.code = code 

assert(issubclass(Error, RuntimeError)) 
try: 
    raise Error(42) 
except RuntimeError as e: 
    assert(e.code == 42) 
except: 
    assert(False) 

aynı genel yaklaşım kullanılabilir tarafından Boost.Python, istisnalar için class_ eşdeğerini yazma ihtiyacını ortadan kaldırarak.

  • C++ nesnesinin bir örneği
  • atıldığında kullanıcı tanımlı Python istisna inşa edecek boost::python::register_exception_translator() ile bir çevirmen kayıt konu tipi açıktaki başlatıcı olamaz: Ancak, ek adımlar ve konuları vardır Python'a. Bu nedenle, Python'da istisnanın bir örneğini oluştururken, konuyu __init__ ile başlatmaya çalışmalıdır. Öte yandan, C++ istisnasının bir örneğini oluştururken, __init__'u önlemek için bir python dönüşümünü kullanmalıdır.
  • one-Python dönüştürücülerinden Python'dan C++'ye geçirilen bir özel durum türünün örneğinin, sarılmış nesnenin bir örneğine dönüştürülmesini sağlamak için kayıt yaptırmak isteyebilir. Aşağıda

yaklaşım yukarıda tarif edilen tam bir örnek demonstrating: Yukarıdaki örnekte

#include <boost/python.hpp> 

namespace exception { 
namespace detail { 

/// @brief Return a Boost.Python object given a borrowed object. 
template <typename T> 
boost::python::object borrowed_object(T* object) 
{ 
    namespace python = boost::python; 
    python::handle<T> handle(python::borrowed(object)); 
    return python::object(handle); 
} 

/// @brief Return a tuple of Boost.Python objects given borrowed objects. 
boost::python::tuple borrowed_objects(
    std::initializer_list<PyObject*> objects) 
{ 
    namespace python = boost::python; 
    python::list objects_; 

    for(auto&& object: objects) 
    { 
    objects_.append(borrowed_object(object)); 
    } 

    return python::tuple(objects_); 
} 

/// @brief Get the class object for a wrapped type that has been exposed 
///  through Boost.Python. 
template <typename T> 
boost::python::object get_instance_class() 
{ 
    namespace python = boost::python; 
    python::type_info type = python::type_id<T>(); 
    const python::converter::registration* registration = 
    python::converter::registry::query(type); 

    // If the class is not registered, return None. 
    if (!registration) return python::object(); 

    return detail::borrowed_object(registration->get_class_object()); 
} 

} // namespace detail 
namespace proxy { 

/// @brief Get the subject object from a proxy. 
boost::python::object get_subject(boost::python::object proxy) 
{ 
    return proxy.attr("__dict__")["_obj"]; 
} 

/// @brief Check if the subject has a subject. 
bool has_subject(boost::python::object proxy) 
{ 
    return boost::python::extract<bool>(
    proxy.attr("__dict__").attr("__contains__")("_obj")); 
} 

/// @brief Set the subject object on a proxy object. 
boost::python::object set_subject(
    boost::python::object proxy, 
    boost::python::object subject) 
{ 
    return proxy.attr("__dict__")["_obj"] = subject; 
} 

/// @brief proxy's __delattr__ that delegates to the subject. 
void del_subject_attr(
    boost::python::object proxy, 
    boost::python::str name) 
{ 
    delattr(get_subject(proxy), name); 
}; 

/// @brief proxy's __getattr__ that delegates to the subject. 
boost::python::object get_subject_attr(
    boost::python::object proxy, 
    boost::python::str name) 
{ 
    return getattr(get_subject(proxy), name); 
}; 

/// @brief proxy's __setattr__ that delegates to the subject. 
void set_subject_attr(
    boost::python::object proxy, 
    boost::python::str name, 
    boost::python::object value) 
{ 
    setattr(get_subject(proxy), name, value); 
}; 

boost::python::dict proxy_attrs() 
{ 
    // By proxying to Boost.Python exposed object, one does not have to 
    // reimplement the entire Boost.Python class_ API for exceptions. 

    // Generic proxying. 
    boost::python::dict attrs; 
    attrs["__detattr__"] = &del_subject_attr; 
    attrs["__getattr__"] = &get_subject_attr; 
    attrs["__setattr__"] = &set_subject_attr; 
    return attrs; 
} 

} // namespace proxy 

/// @brief Registers from-Python converter for an exception type. 
template <typename Subject> 
struct from_python_converter 
{ 
    from_python_converter() 
    { 
    boost::python::converter::registry::push_back(
     &convertible, 
     &construct, 
     boost::python::type_id<Subject>() 
    ); 
    } 

    static void* convertible(PyObject* object) 
    { 
    namespace python = boost::python; 
    python::object subject = proxy::get_subject(
     detail::borrowed_object(object) 
    ); 

    // Locate registration based on the C++ type. 
    python::object subject_instance_class = 
     detail::get_instance_class<Subject>(); 
    if (!subject_instance_class) return nullptr; 

    bool is_instance = (1 == PyObject_IsInstance(
     subject.ptr(), 
     subject_instance_class.ptr() 
    )); 
    return is_instance 
     ? object 
     : nullptr; 
    } 

    static void construct(
    PyObject* object, 
    boost::python::converter::rvalue_from_python_stage1_data* data) 
    { 
    // Object is a borrowed reference, so create a handle indicting it is 
    // borrowed for proper reference counting. 
    namespace python = boost::python; 
    python::object proxy = detail::borrowed_object(object); 

    // Obtain a handle to the memory block that the converter has allocated 
    // for the C++ type. 
    using storage_type = 
     python::converter::rvalue_from_python_storage<Subject>; 
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes; 

    // Copy construct the subject into the converter storage block. 
    python::object subject = proxy::get_subject(proxy); 
    new (storage) Subject(python::extract<const Subject&>(subject)()); 

    // Indicate the object has been constructed into the storage. 
    data->convertible = storage; 
    } 

}; 

/// @brief Expose an exception type in the current scope, that embeds and 
//   proxies to the Wrapped type. 
template <typename Wrapped> 
class exception: 
    boost::python::object 
{ 
public: 

    /// @brief Expose a RuntimeError exception type with the provided name. 
    exception(const char* name) : exception(name, {}) {} 

    /// @brief Expose an expcetion with the provided name, deriving from the 
    ///  borrowed base type. 
    exception(
    const char* name, 
    PyObject* borrowed_base 
) : exception(name, {borrowed_base}) {} 

    /// @brief Expose an expcetion with the provided name, deriving from the 
    ///  multiple borrowed base type. 
    exception(
    const char* name, 
    std::initializer_list<PyObject*> borrowed_bases 
) : exception(name, detail::borrowed_objects(borrowed_bases)) {} 

    /// @brief Expose an expcetion with the provided name, deriving from tuple 
    ///  of bases. 
    exception(
    const char* name, 
    boost::python::tuple bases) 
    { 
    // Default to deriving from Python's RuntimeError. 
    if (!bases) 
    { 
     bases = make_tuple(detail::borrowed_object(PyExc_RuntimeError)); 
    } 

    register_exception_type(name, bases); 
    patch_initializer(); 
    register_translator(); 
    } 

public: 

    exception& enable_from_python() 
    { 
    from_python_converter<Wrapped>{}; 
    return *this; 
    } 

private: 

    /// @brief Handle to this class object. 
    boost::python::object this_class_object() { return *this; } 

    /// @brief Create the Python exception type and install it into this object. 
    void register_exception_type(
    std::string name, 
    boost::python::tuple bases) 
    { 
    // Copy the instance class' name and scope. 
    namespace python = boost::python; 
    auto scoped_name = python::scope().attr("__name__") + "." + name; 

    // Docstring handling. 
    auto docstring = detail::get_instance_class<Wrapped>().attr("__doc__"); 

    // Create exception dervied from the desired exception types, but with 
    // the same name as the Boost.Python class. This is required because 
    // Python exception types and Boost.Python classes have incompatiable 
    // layouts. 
    // >> type_name = type(fullname, (bases,), {proxying attrs}) 
    python::handle<> handle(PyErr_NewExceptionWithDoc(
     python::extract<char*>(scoped_name)(), 
     docstring ? python::extract<char*>(docstring)() : nullptr, 
     bases.ptr(), 
     proxy::proxy_attrs().ptr() 
    )); 

    // Assign the exception type to this object. 
    python::object::operator=(python::object{handle}); 

    // Insert this object into current scope. 
    setattr(python::scope(), name, this_class_object()); 
    } 

    /// @brief Patch the initializer to install the delegate object. 
    void patch_initializer() 
    { 
    namespace python = boost::python; 
    auto original_init = getattr(this_class_object(), "__init__"); 

    // Use raw function so that *args and **kwargs can transparently be 
    // passed to the initializers. 
    this_class_object().attr("__init__") = python::raw_function(
     [original_init](
     python::tuple args, // self + *args 
     python::dict kwargs) // **kwargs 
     { 
     original_init(*args, **kwargs); 
     // If the subject does not exists, then create it. 
     auto self = args[0]; 
     if (!proxy::has_subject(self)) 
     { 
      proxy::set_subject(self, detail::get_instance_class<Wrapped>()(
      *args[python::slice(1, python::_)], // args[1:] 
      **kwargs 
     )); 
     } 

     return python::object{}; // None 
     }); 
    } 

    // @brief Register translator within the Boost.Python exception handling 
    //  chaining. This allows for an instance of the wrapped type to be 
    //  converted to an instance of this exception. 
    void register_translator() 
    { 
    namespace python = boost::python; 
    auto exception_type = this_class_object(); 
    python::register_exception_translator<Wrapped>(
     [exception_type](const Wrapped& proxied_object) 
     { 
     // Create the exception object. If a subject is not installed before 
     // the initialization of the instance, then a subject will attempt to 
     // be installed. As the subject may not be constructible from Python, 
     // manually inject a subject after construction, but before 
     // initialization. 
     python::object exception_object = exception_type.attr("__new__")(
      exception_type 
     ); 

     proxy::set_subject(exception_object, python::object(proxied_object)); 

     // Initialize the object. 
     exception_type.attr("__init__")(exception_object); 

     // Set the exception. 
     PyErr_SetObject(exception_type.ptr(), exception_object.ptr()); 
     }); 
    } 
}; 

// @brief Visitor that will turn the visited class into an exception, 
/// enabling exception translation. 
class export_as_exception 
    : public boost::python::def_visitor<export_as_exception> 
{ 
public: 

    /// @brief Expose a RuntimeError exception type. 
    export_as_exception() : export_as_exception({}) {} 

    /// @brief Expose an expcetion type deriving from the borrowed base type. 
    export_as_exception(PyObject* borrowed_base) 
    : export_as_exception({borrowed_base}) {} 

    /// @brief Expose an expcetion type deriving from multiple borrowed 
    ///  base types. 
    export_as_exception(std::initializer_list<PyObject*> borrowed_bases) 
    : export_as_exception(detail::borrowed_objects(borrowed_bases)) {} 

    /// @brief Expose an expcetion type deriving from multiple bases. 
    export_as_exception(boost::python::tuple bases) : bases_(bases) {} 

private: 

    friend class boost::python::def_visitor_access; 

    template <typename Wrapped, typename ...Args> 
    void visit(boost::python::class_<Wrapped, Args...> instance_class) const 
    { 
    exception<Wrapped>{ 
     boost::python::extract<const char*>(instance_class.attr("__name__"))(), 
     bases_ 
    }; 
    } 

private: 
    boost::python::tuple bases_; 
}; 

} // namespace exception 

struct foo { int code; }; 

struct spam 
{ 
    spam(int code): code(code) {} 
    int code; 
}; 

BOOST_PYTHON_MODULE(example) 
{ 
    namespace python = boost::python; 

    // Expose `foo` as `example.FooError`. 
    python::class_<foo>("FooError", python::no_init) 
    .def_readonly("code", &foo::code) 
    // Redefine the exposed `example.FooError` class as an exception. 
    .def(exception::export_as_exception(PyExc_RuntimeError)); 
    ; 

    // Expose `spam` as `example.Spam`. 
    python::class_<spam>("Spam", python::init<int>()) 
    .def_readwrite("code", &spam::code) 
    ; 

    // Also expose `spam` as `example.SpamError`. 
    exception::exception<spam>("SpamError", {PyExc_IOError, PyExc_SystemError}) 
    .enable_from_python() 
    ; 

    // Verify from-python. 
    python::def("test_foo", +[](int x){ throw foo{x}; }); 
    // Verify to-Python and from-Python. 
    python::def("test_spam", +[](const spam& error) { throw error; }); 
} 

, C++ foo tip example.FooError olarak maruz, daha sonra example.FooError bir özel türüne yeniden alır olduğu RuntimeError ve orijinal example.FooError proxy'lerinden türetilmiştir. Ayrıca, C++ spam türü example.Spam olarak görüntülenir ve IOError ve SystemError ve example.Spam proxy'lerinden türetilen istisna türü example.SpamError tanımlanır. example.SpamError ayrıca C++ spam türüne de dönüştürülebilir.

Etkileşimli kullanımı:

Biraz konu önerisi kapalı
>>> import example 
>>> try: 
...  example.test_foo(100) 
... except example.FooError as e: 
...  assert(isinstance(e, RuntimeError)) 
...  assert(e.code == 100) 
... except: 
...  assert(False) 
... 
>>> try: 
...  example.test_foo(101) 
... except RuntimeError as e: 
...  assert(isinstance(e, example.FooError)) 
...  assert(e.code == 101) 
... except: 
...  assert(False) 
... 
... spam_error = example.SpamError(102) 
... assert(isinstance(spam_error, IOError)) 
... assert(isinstance(spam_error, SystemError)) 
>>> try: 
...  example.test_spam(spam_error) 
... except IOError as e: 
...  assert(e.code == 102) 
... except: 
...  assert(False) 
... 
+0

Bu oldukça şaşırtıcı. Temel olarak, bir C++ istisna hiyerarşisine sahip olmak, Python'da Boost ile bunu ifade etmenin bir yolu olmadığından, çıktı mı? – Barry