Skip to content

Bind a custom exception class, with attributes/methods #1281

@Sarcasm

Description

@Sarcasm

Hello,
I asked quickly on gitter (https://gitter.im/pybind/Lobby?at=5a7b7f1c6117191e610a8304),
but I feel like this may take a bit longer to resolve.

I'd like to bind a custom exception class to Python.
This custom exception class has attributes,
which I want to expose to Python.

This is very similar to the std::system_error,
which has a code() member function:

If this issue is sorted out,
maybe as a result a std::system_error binding could be provided?
There was interests shown here:

My issue right now, is how to inherit a Python built-in type, more precisely, PyExc_RuntimeError.
I'm not yet trying to register a custom translator.

If I attempt to use the code proposed on Gitter:

py::class_<CppException>(m, "PyException",
    py::reinterpret_borrow<py::object>(PyExc_RuntimeError))
        .def(...)

I get an assertion error when importing the module:

python: pybind11/detail/class.h:591:
PyObject* pybind11::detail::make_new_python_type(const pybind11::detail::type_record&):
Assertion `rec.dynamic_attr ?  (((type)->tp_flags & ((1L<<14))) != 0) 
                            : !(((type)->tp_flags & ((1L<<14))) != 0)' failed.

I have a little piece of code that I use to understand how things work,
when inheriting a PyObject *.

#include <pybind11/pybind11.h>

namespace py = pybind11;

struct FooBase { int base_value = 444; };
struct Foo // : FooBase // 1. commented on purpose
{
    int value = 42;
};

PYBIND11_MODULE(p11, m)
{
    auto pyfoobase = py::class_<FooBase>(m, "PyFooBase");
    pyfoobase.def(py::init<>());
    pyfoobase.def_readonly("base_value", &FooBase::base_value);

    PyObject* obj = pyfoobase.ptr();
    // obj = PyExc_RuntimeError; // 2. uncomment to get the assertion error

    py::class_<Foo>(m, "PyFoo", py::reinterpret_borrow<py::object>(obj))
        .def(py::init<>())
        .def_readonly("value", &Foo::value);
}

If I run this code, everything compiles
even if FooBase is not a base class of Foo.
However, if I run the code, base_value returns 42, not 444:

$ python -c 'import p11; print(p11.PyFoo().base_value)'
42

If I uncomment struct Foo // : FooBase => struct Foo : FooBase,
I get the expected behavior:

$ python -c 'import p11; print(p11.PyFoo().base_value)'
444

It kind of make sense to me,
but I'm wondering what to do if I wanted to use a Python builtin type as a base,
instead of my FooBase class:

- PyObject* obj = pyfoobase.ptr();
+ PyObject* obj = PyExc_RuntimeError;

How does my PyFoo type store the base class data?
In case of FooBase, the data is stored when Foo inherits FooBase.
If I inherit PyExc_RuntimeError, what would be my base class?
I don't really want add anything Python to my base class,
so I assume the right thing to do would be to give this base class information
only to the PyFoo wrapper.

How can I inherit a builtin Python type such as PyExc_RuntimeError?

Related issue:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions