RcsPySim
A robot control and simulation library
pybind_dict_utils.h
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright (c) 2020, Fabio Muratore, Honda Research Institute Europe GmbH, and
3  Technical University of Darmstadt.
4  All rights reserved.
5 
6  Redistribution and use in source and binary forms, with or without
7  modification, are permitted provided that the following conditions are met:
8  1. Redistributions of source code must retain the above copyright
9  notice, this list of conditions and the following disclaimer.
10  2. Redistributions in binary form must reproduce the above copyright
11  notice, this list of conditions and the following disclaimer in the
12  documentation and/or other materials provided with the distribution.
13  3. Neither the name of Fabio Muratore, Honda Research Institute Europe GmbH,
14  or Technical University of Darmstadt, nor the names of its contributors may
15  be used to endorse or promote products derived from this software without
16  specific prior written permission.
17 
18  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  DISCLAIMED. IN NO EVENT SHALL FABIO MURATORE, HONDA RESEARCH INSTITUTE EUROPE GMBH,
22  OR TECHNICAL UNIVERSITY OF DARMSTADT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  POSSIBILITY OF SUCH DAMAGE.
29 *******************************************************************************/
30 
31 #ifndef PYBIND_DICT_UTILS_H_
32 #define PYBIND_DICT_UTILS_H_
33 
34 #include <pybind11/pybind11.h>
35 
36 #include <dictobject.h>
37 
38 namespace py = pybind11;
39 
40 // Some utilities for quickly accessing dict elements. Mainly intended for use with kwargs, but also useful otherwise
41 // these take a py::dict reference to ensure that the object is properly cast, but save reference counts
42 
43 /**
44  * Get the string-labeled item from the dict and return it as python object.
45  * Returns default_ if item not found.
46  *
47  * This is designed to work similar to python's dict.get()
48  *
49  * @param dict python dictionary
50  * @param key key string
51  * @param default_ default value to return if not found. defaults to None.
52  * @param noneIsEmpty set to false to return None values explicitly. If true, None is replaced with default_.
53  * @return the value for key if found, else default.
54  */
55 inline py::object get(const py::dict& dict, const char* key, py::handle default_ = py::none(), bool noneIsEmpty = true)
56 {
57  PyObject* result = PyDict_GetItemString(dict.ptr(), key);
58  if (result && (!noneIsEmpty || result != Py_None)) {
59  return py::reinterpret_borrow<py::object>(result);
60  }
61  else {
62  PyErr_Clear();
63  return py::reinterpret_borrow<py::object>(default_);
64  }
65 }
66 
67 /**
68  * Get the string-labeled item from the dict and return it as converted c++ value.
69  * Returns default_ if item not found.
70  *
71  * This is designed to work similar to python's dict.get().
72  *
73  * A special overload of this function handles pointer types, which are normally not allowed as cast return type.
74  *
75  * @param dict python dictionary
76  * @param key key string
77  * @param default_ default value to return if not found.
78  * @param noneIsEmpty set to false to return None values explicitly. If true, None is replaced with default_.
79  * @return the value for key if found, else default.
80  */
81 template<typename T, typename std::enable_if<!std::is_pointer<T>::value, int>::type = 0>
82 inline T get_cast(const py::dict& dict, const char* key, T default_, bool noneIsEmpty = true)
83 {
84  PyObject* result = PyDict_GetItemString(dict.ptr(), key);
85  if (result && (!noneIsEmpty || result != Py_None)) {
86  return py::handle(result).cast<T>();
87  }
88  else {
89  PyErr_Clear();
90  return std::forward<T>(default_);
91  }
92 }
93 
94 // special case for returning pointers, which allows default to be NULL.
95 // here, we need to return something storing the type caster, since the regular cast doesn't support pointers
96 namespace detail
97 {
98 
99 // this object holds the type caster, and otherwise behaves as if it were a T*
100 template<typename T>
101 struct cast_pointer_proxy
102 {
103 private:
104  py::detail::make_caster<T> caster;
105  T* ptr;
106 public:
107  cast_pointer_proxy(T* val)
108  {
109  ptr = val;
110  }
111 
112  cast_pointer_proxy(const py::handle& handle)
113  {
114  if (handle.is_none()) {
115  // some type casters don't support none
116  ptr = NULL;
117  }
118  else {
119  py::detail::load_type(caster, handle);
120  ptr = py::detail::cast_op<T*>(caster);
121  }
122  }
123 
124  operator T*()
125  {
126  return ptr;
127  }
128 
129  T* operator->()
130  {
131  return ptr;
132  }
133 
134 };
135 
136 }
137 
138 
139 /**
140  * Get the string-labeled item from the dict and return it as converted c++ pointer value value.
141  * Returns default_ if item not found.
142  *
143  * This is designed to work similar to python's dict.get().
144  *
145  * The return value is actually a wrapper to make sure the pointer's target does not go out of scope.
146  * It has operator overloads to be usable like a regular pointer.
147  *
148  * @param dict python dictionary
149  * @param key key string
150  * @param default_ default value to return if not found. defaults to NULL.
151  * @return the value for key if found, else default.
152  */
153 template<typename T, typename std::enable_if<std::is_pointer<T>::value, int>::type = 0>
154 inline detail::cast_pointer_proxy<typename std::remove_pointer<T>::type>
155 get_cast(const py::dict& dict, const char* key, T&& default_ = NULL)
156 {
157  if (PyObject* result = PyDict_GetItemString(dict.ptr(), key)) {
158  return {result};
159  }
160  else {
161  PyErr_Clear();
162  return {default_};
163  }
164 }
165 
166 
167 /**
168  * Get the string-labeled item from the dict, convert it and set it to the var parameter
169  * Returns false if item not found.
170  *
171  * This is designed to work similar to python's dict.get().
172  *
173  * @param dict python dictionary
174  * @param key key string
175  * @param[out] var output var ref. not modified if the parameter is not found.
176  * @return true if found
177  */
178 template<typename T>
179 inline bool try_get(const py::dict& dict, const char* key, T& var)
180 {
181  if (PyObject* result = PyDict_GetItemString(dict.ptr(), key)) {
182  var = pybind11::handle(result).cast<T>();
183  return true;
184  }
185  else {
186  PyErr_Clear();
187  return false;
188  }
189 }
190 
191 /**
192  * Get the string-labeled item from the dict, convert it and set it to the var parameter
193  * Returns false if item not found.
194  *
195  * This is designed to work similar to python's dict.get().
196  *
197  * @param dict python dictionary
198  * @param key key string
199  * @param[out] var output var ref. not modified if the parameter is not found.
200  * @return true if found
201  */
202 template<typename T>
203 inline bool try_get(const py::dict& dict, const std::string& key, T& var)
204 {
205  if (PyObject* result = PyDict_GetItemString(dict.ptr(), key.c_str())) {
206  var = pybind11::handle(result).cast<T>();
207  return true;
208  }
209  else {
210  PyErr_Clear();
211  return false;
212  }
213 }
214 
215 
216 #endif /* PYBIND_DICT_UTILS_H_ */
T get_cast(const py::dict &dict, const char *key, T default_, bool noneIsEmpty=true)
bool try_get(const py::dict &dict, const char *key, T &var)