How to wrap a singleton class using pybind11?

2019-07-20 18:33发布

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)?

标签: pybind11
3条回答
地球回转人心会变
2楼-- · 2019-07-20 19:03

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);
查看更多
该账号已被封号
3楼-- · 2019-07-20 19:06

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

查看更多
等我变得足够好
4楼-- · 2019-07-20 19:25

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

查看更多
登录 后发表回答