How to wrap a singleton class using pybind11?

2019-07-20 19:00发布

问题:

I have a singleton class in C++ (no public constructor, C++ programmers call class.instance() to create the singleton or return the existing one).

I'd prefer to hide this at the Python level. If I was writing a Python singleton, I'd handle that in __new__. If a class has no public constructor I don't think I can create an __init__ wrapper (my attempts at that have failed). I saw no mention of __new__ in the pybind11 docs (though might have missed it, and Google seems happy to elide underscores an return pages containing "new", with no mention of __new__).

Is there a singleton recipe for pybind11 (or even Boost.Python)?

回答1:

You don't need to expose __init__ if you don't instantiate your class from Python. As for your question, you can try something like this:

py::class_<CppSingle>(mod, "Single")
.def_static("__new__", [](py:object) { return CppSingle::instance(); )},
 py::return_value_policy::reference_internal);


回答2:

I'm not sure about pybind11, but I believe it should be possible to wrap your class using Boost.Python. (Your question reads "or even Boost.Python"...)

Use noncopyable and/or no_init:

class_<Klass, boost::noncopyable>("Klass", no_init)
    .staticmethod("instance")
    ;

https://mail.python.org/pipermail/cplusplus-sig/2004-March/006647.html



回答3:

assuming your singleton class looks like this:

class MySingleton{
    // private constructor here
public:
    MySingleton& instance();    
};

You could wrap it up like so:

    py::class_<MySingleton, std::unique_ptr<MySingleton, py::nodelete>>(m, "MySingleton")
    .def(py::init([](){ 
        return std::unique_ptr<MySingleton, py::nodelete>>(&MySingleton::instance());
    });

Key here is to use py::nodelete so the destructor is not referenced (and your c++ singleton is not destructed when the unique_ptrs used by multiple python instances are garbage-collected).

That code also relies on the custom constructor support introduced with pybind11 v2.2.0 (August 31st, 2017) which allows us to wrap a lambda instead of a constructor inside the init.

References
- Pybind11 v2.2.0 changelog
- Pybind11 doc about custom constructors
- Pybind11 doc on non-public destructors



标签: pybind11