@@ -36,7 +36,7 @@ namespace SExtractor {
3636 * Elements::Exception if either the call or the extract throw a Python exception
3737 */
3838template <typename R, typename ...T>
39- R py_call_wrapper (py::object func, T... args) {
39+ R py_call_wrapper (const py::object& func, T... args) {
4040 try {
4141 return py::extract<R>(func (args...));
4242 }
@@ -45,13 +45,47 @@ R py_call_wrapper(py::object func, T... args) {
4545 }
4646}
4747
48+ /* *
49+ * @brief Hold a reference to a Python object
50+ * @details
51+ * A boost::python::object contains a pointer to the underlying Python struct, which is
52+ * copied as-is (shared) when copied. When the boost::python::object is destroyed, it checks,
53+ * and then decrements, the reference count. This destruction is *not* thread safe, as the pointer
54+ * is not protected by a mutex or anything.
55+ * This class holds a single reference to the Python object, and relies on the mechanism of
56+ * std::shared_ptr to destroy the object once there is no one using it. std::shared_ptr *is*
57+ * thread safe, unlike boost::python::object.
58+ */
59+ class PyObjectHolder {
60+ public:
61+ PyObjectHolder (py::object&& obj): m_obj_ptr(std::make_shared<py::object>(obj)) {}
62+
63+ PyObjectHolder (const PyObjectHolder&) = default ;
64+ PyObjectHolder (PyObjectHolder&&) = default ;
65+
66+ operator const py::object&() const {
67+ return *m_obj_ptr;
68+ }
69+
70+ const py::object& operator *() const {
71+ return *m_obj_ptr;
72+ }
73+
74+ py::object attr (const char *name) {
75+ return m_obj_ptr->attr (name);
76+ }
77+
78+ private:
79+ std::shared_ptr<py::object> m_obj_ptr;
80+ };
81+
4882ModelFittingConfig::ModelFittingConfig (long manager_id) : Configuration(manager_id) {
4983 declareDependency<PythonConfig>();
5084}
5185
5286void ModelFittingConfig::initialize (const UserValues&) {
5387 for (auto & p : getDependency<PythonConfig>().getInterpreter ().getConstantParameters ()) {
54- py::object py_value_func = p.second .attr (" get_value" )();
88+ auto py_value_func = PyObjectHolder ( p.second .attr (" get_value" )() );
5589 auto value_func = [py_value_func] (const SourceInterface& o) -> double {
5690 ObjectInfo oi {o};
5791 return py_call_wrapper<double >(py_value_func, oi);
@@ -61,25 +95,25 @@ void ModelFittingConfig::initialize(const UserValues&) {
6195 }
6296
6397 for (auto & p : getDependency<PythonConfig>().getInterpreter ().getFreeParameters ()) {
64- py::object py_init_value_func = p.second .attr (" get_init_value" )();
98+ auto py_init_value_func = PyObjectHolder ( p.second .attr (" get_init_value" )() );
6599 auto init_value_func = [py_init_value_func] (const SourceInterface& o) -> double {
66100 ObjectInfo oi {o};
67101 return py_call_wrapper<double >(py_init_value_func, oi);
68102 };
69103
70- py::object py_range_obj = p.second .attr (" get_range" )();
104+ auto py_range_obj = PyObjectHolder ( p.second .attr (" get_range" )() );
71105
72106 std::shared_ptr<FlexibleModelFittingConverterFactory> converter;
73107 std::string type_string (py::extract<char const *>(py_range_obj.attr (" __class__" ).attr (" __name__" )));
74108 if (type_string == " Unbounded" ) {
75- py::object py_factor_func = py_range_obj.attr (" get_normalization_factor" )();
109+ auto py_factor_func = PyObjectHolder ( py_range_obj.attr (" get_normalization_factor" )() );
76110 auto factor_func = [py_factor_func] (double init, const SourceInterface& o) -> double {
77111 ObjectInfo oi {o};
78112 return py_call_wrapper<double >(py_factor_func, init, oi);
79113 };
80114 converter = std::make_shared<FlexibleModelFittingUnboundedConverterFactory>(factor_func);
81115 } else if (type_string == " Range" ) {
82- py::object py_range_func = py_range_obj.attr (" get_limits" )();
116+ auto py_range_func = PyObjectHolder ( py_range_obj.attr (" get_limits" )() );
83117 auto range_func = [py_range_func] (double init, const SourceInterface& o) -> std::pair<double , double > {
84118 ObjectInfo oi {o};
85119 py::tuple range = py_call_wrapper<py::tuple>(py_range_func, init, oi);
@@ -102,7 +136,7 @@ void ModelFittingConfig::initialize(const UserValues&) {
102136 }
103137
104138 for (auto & p : getDependency<PythonConfig>().getInterpreter ().getDependentParameters ()) {
105- py::object py_func = p.second .attr (" func" );
139+ auto py_func = PyObjectHolder ( p.second .attr (" func" ) );
106140 std::vector<std::shared_ptr<FlexibleModelFittingParameter>> params {};
107141 py::list param_ids = py::extract<py::list>(p.second .attr (" params" ));
108142 for (int i = 0 ; i < py::len (param_ids); ++i) {
@@ -113,7 +147,7 @@ void ModelFittingConfig::initialize(const UserValues&) {
113147 auto dependent_func = [py_func](const std::shared_ptr<CoordinateSystem> &cs, const std::vector<double > ¶ms) -> double {
114148 try {
115149 PythonInterpreter::getSingleton ().setCoordinateSystem (cs);
116- return py::extract<double >(py_func (*py::tuple (params)));
150+ return py::extract<double >((* py_func) (*py::tuple (params)));
117151 }
118152 catch (const py::error_already_set&) {
119153 throw pyToElementsException (logger);
@@ -182,12 +216,12 @@ void ModelFittingConfig::initialize(const UserValues&) {
182216 auto & prior = p.second ;
183217 int param_id = py::extract<int >(prior.attr (" param" ));
184218 auto param = m_parameters[param_id];
185- py::object py_value_func = prior.attr (" value" );
219+ auto py_value_func = PyObjectHolder ( prior.attr (" value" ) );
186220 auto value_func = [py_value_func] (const SourceInterface& o) -> double {
187221 ObjectInfo oi {o};
188222 return py_call_wrapper<double >(py_value_func, oi);
189223 };
190- py::object py_sigma_func = prior.attr (" sigma" );
224+ auto py_sigma_func = PyObjectHolder ( prior.attr (" sigma" ) );
191225 auto sigma_func = [py_sigma_func] (const SourceInterface& o) -> double {
192226 ObjectInfo oi {o};
193227 return py_call_wrapper<double >(py_sigma_func, oi);
0 commit comments