pybind / pybind/pybind11

Bind a custom exception class, with attributes/methods

Open
#1,281 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
18k
Forks
2.3k
Avg merge
5d 17h
Merged PRs (30d)
10

Description

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:

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal reproducer in the issue and the assertion at pybind11/detail/class.h:591, then compare the FooBase inheritance case with PyExc_RuntimeError. The work is done when a custom C++ exception can inherit the Python RuntimeError type while exposing its attributes or methods without the import-time assertion.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.