Skip to content

[FEAT] Cannot implicitly convert from None. #2778

@crisluengo

Description

@crisluengo

I am going to address this issue one more time. It was first brought up in #642, then again in #1382.

In short, we cannot make a custom type that implicitly converts from None. @bstaletic suggested a solution that doesn't work.

Consider the following example code (paraphrased, might have typos):

#include "pydip.h"

class A {
public:
   int value = 0;
   A() = default;
   A(int v) : value(v) {};
}

int X(A const&) {
   return A.value;
}

PYBIND11_MODULE(mymodule, m) {
   auto a = py::class_<A>(m, "A");
   a(py::init<>());
   a("__init__", [](py::object const& o) { // @bstaletic's solution
      if (o.is_none()) {
         return A{};
      }
      throw py::type_error("boom");
   });
   //a(py::init([](py::none const&) { return A{}; }));  // I had tried this one too
   py::implicitly_convertible<py::none, A>();
   a(py::init<int>());
   py::implicitly_convertible<int, A>();

   m.def("X", X);
}

I can now do in Python:

a = A(None)
a = A(1)
X(a)
X(1)

But I cannot do

X(None)

However, if I edit pybind11/cast.h, at the top of bool load_impl(handle src, bool convert), where it says:

        if (src.is_none()) {
            // Defer accepting None to other overloads (if we aren't in convert mode):
            if (!convert) return false;
            value = nullptr;
            return true;
        }

If I remove the value = nullptr; return true; lines, then the above all works correctly.

I have not found any downsides to doing so. This means that the custom converter function gets a py::none object as input, it can choose to reject that or accept that. Any conversion function already tests what the type of the input object is, so this should not affect any existing code.

I have been using this "fix" for some months now, but would prefer a solution that doesn't require patching pybind11.


In real life, the above example is for an image class where the default-constructed image has no pixels, and is used to signal "I don't need this input" to specific functions. In C++ one can use {} for a default-constructed object, None is the most Pythonic substitution. See this other comment for details.

Sure, I could write a function for X(None), but this is for a large project with a lot of functions, and creating multiple versions of each for various inputs being None is not doable.

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