I have a Singleton defined in C++ that I want to expose to Python. Python seems to create a new instance of the Singleton no matter how I bind it. My main code is in a core library, which the Python module is also linked to.
cmake_minimum_required(VERSION 3.12)
project(pybind-singleton)
cmake_policy(SET CMP0148 NEW)
set(CMAKE_CXX_STANDARD 17)
find_package(pybind11 CONFIG REQUIRED)
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
add_library(my_lib SHARED
src/Singleton.cpp
)
message(STATUS "Python include dirs: " ${PYTHON_INCLUDE_DIRS})
message(STATUS "pybind include dirs: " ${pybind11_INCLUDE_DIRS})
target_include_directories(my_lib PUBLIC include ${PYTHON_INCLUDE_DIRS} ${pybind11_INCLUDE_DIRS})
target_link_libraries(my_lib PUBLIC Python3::Python)
pybind11_add_module(my_module src/binding.cpp)
target_link_libraries(my_module PUBLIC my_lib)
add_executable(${CMAKE_PROJECT_NAME} src/main.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC my_lib)
class Singleton
{
public:
static Singleton &GetInstance()
{
static Singleton instance;
return instance;
}
Singleton(Singleton const &) = delete;
void operator=(Singleton const &) = delete;
std::string test;
void Init();
void PrintTest();
private:
Singleton(){};
};
namespace py = pybind11;
PYBIND11_MODULE(my_module, m)
{
py::class_<Singleton>(m, "Singleton")
.def_static(
"print_test", []()
{ Singleton::GetInstance().PrintTest(); },
py::return_value_policy::reference)
.def_static(
"get_test_val", []()
{
Singleton &singleton = Singleton::GetInstance();
return singleton.test; },
py::return_value_policy::reference)
.def_static(
"get_instance", &Singleton::GetInstance, py::return_value_policy::reference)
.def_readwrite("test", &Singleton::test);
}
from my_module import Singleton
print('Accessing Singleton...')
test_val = Singleton.get_test_val()
print('Test val:', test_val if test_val != '' else 'None')
print('--')
Singleton.print_test()
print('--')
instance = Singleton.get_instance()
print(instance)
print(instance.test)
instance.test = 'Python\'s instance'
print(instance.test)
instance.print_test()
% ./pybind-singleton
Instancing Singleton...
/Users/jordan/dev/pybind-singleton/src/Singleton.cpp: 12
/Users/jordan/dev/pybind-singleton/src/Singleton.cpp: 12
C++'s instance
------------
Running init.py
Accessing Singleton...
Test val: None
--
/Users/jordan/dev/pybind-singleton/src/Singleton.cpp: 12
--
<my_module.Singleton object at 0x1024b9fb0>
Python's instance
/Users/jordan/dev/pybind-singleton/src/Singleton.cpp: 12
Python's instance
I've made up a minimal example in this repo; some of the code has been omitted from above for brevity: https://github.com/jordanlevy96/pybind-singleton
The extern keyword was made for this. This is explained well in These StackOverflow Answers. In short, extern tells the linker that the different modules need to reference the same underlying object.
I also needed to remove static readwrite permission, i.e.
instance = Singleton.get_instance()
instance.test = 'New Value'
print(instance.test)
changes to
Singleton.set_test_val('New Value')
print(Singleton.get_test_val())