13
13
#include " detail/common.h"
14
14
15
15
#include < deque>
16
+ #include < initializer_list>
16
17
#include < list>
17
18
#include < map>
18
19
#include < ostream>
35
36
PYBIND11_NAMESPACE_BEGIN (PYBIND11_NAMESPACE)
36
37
PYBIND11_NAMESPACE_BEGIN(detail)
37
38
39
+ //
40
+ // Begin: Equivalent of
41
+ // https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438
42
+ /*
43
+ The three `PyObjectTypeIsConvertibleTo*()` functions below are
44
+ the result of converging the behaviors of pybind11 and PyCLIF
45
+ (http://github.com/google/clif).
46
+
47
+ Originally PyCLIF was extremely far on the permissive side of the spectrum,
48
+ while pybind11 was very far on the strict side. Originally PyCLIF accepted any
49
+ Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as
50
+ the elements were convertible. The obvious (in hindsight) problem was that
51
+ any empty Python iterable could be passed to any of these C++ types, e.g. `{}`
52
+ was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments.
53
+
54
+ The functions below strike a practical permissive-vs-strict compromise,
55
+ informed by tens of thousands of use cases in the wild. A main objective is
56
+ to prevent accidents and improve readability:
57
+
58
+ - Python literals must match the C++ types.
59
+
60
+ - For C++ `set`: The potentially reducing conversion from a Python sequence
61
+ (e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going
62
+ through a Python `set`.
63
+
64
+ - However, a Python `set` can still be passed to a C++ `vector`. The rationale
65
+ is that this conversion is not reducing. Implicit conversions of this kind
66
+ are also fairly commonly used, therefore enforcing explicit conversions
67
+ would have an unfavorable cost : benefit ratio; more sloppily speaking,
68
+ such an enforcement would be more annoying than helpful.
69
+ */
70
+
71
+ inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj,
72
+ std::initializer_list<const char *> tp_names) {
73
+ if (PyType_Check (obj)) {
74
+ return false ;
75
+ }
76
+ const char *obj_tp_name = Py_TYPE (obj)->tp_name ;
77
+ for (const auto *tp_name : tp_names) {
78
+ if (std::strcmp (obj_tp_name, tp_name) == 0 ) {
79
+ return true ;
80
+ }
81
+ }
82
+ return false ;
83
+ }
84
+
85
+ inline bool PyObjectTypeIsConvertibleToStdVector (PyObject *obj) {
86
+ if (PySequence_Check (obj) != 0 ) {
87
+ return !PyUnicode_Check (obj) && !PyBytes_Check (obj);
88
+ }
89
+ return (PyGen_Check (obj) != 0 ) || (PyAnySet_Check (obj) != 0 )
90
+ || PyObjectIsInstanceWithOneOfTpNames (
91
+ obj, {" dict_keys" , " dict_values" , " dict_items" , " map" , " zip" });
92
+ }
93
+
94
+ inline bool PyObjectTypeIsConvertibleToStdSet (PyObject *obj) {
95
+ return (PyAnySet_Check (obj) != 0 ) || PyObjectIsInstanceWithOneOfTpNames (obj, {" dict_keys" });
96
+ }
97
+
98
+ inline bool PyObjectTypeIsConvertibleToStdMap (PyObject *obj) {
99
+ if (PyDict_Check (obj)) {
100
+ return true ;
101
+ }
102
+ // Implicit requirement in the conditions below:
103
+ // A type with `.__getitem__()` & `.items()` methods must implement these
104
+ // to be compatible with https://docs.python.org/3/c-api/mapping.html
105
+ if (PyMapping_Check (obj) == 0 ) {
106
+ return false ;
107
+ }
108
+ PyObject *items = PyObject_GetAttrString (obj, " items" );
109
+ if (items == nullptr ) {
110
+ PyErr_Clear ();
111
+ return false ;
112
+ }
113
+ bool is_convertible = (PyCallable_Check (items) != 0 );
114
+ Py_DECREF (items);
115
+ return is_convertible;
116
+ }
117
+
118
+ //
119
+ // End: Equivalent of clif/python/runtime.cc
120
+ //
121
+
38
122
// / Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
39
123
// / forwarding a container element). Typically used indirect via forwarded_type(), below.
40
124
template <typename T, typename U>
@@ -66,24 +150,40 @@ struct set_caster {
66
150
}
67
151
void reserve_maybe (const anyset &, void *) {}
68
152
69
- public:
70
- bool load (handle src, bool convert) {
71
- if (!isinstance<anyset>(src)) {
72
- return false ;
73
- }
74
- auto s = reinterpret_borrow<anyset>(src);
75
- value.clear ();
76
- reserve_maybe (s, &value);
77
- for (auto entry : s) {
153
+ bool convert_iterable (const iterable &itbl, bool convert) {
154
+ for (const auto &it : itbl) {
78
155
key_conv conv;
79
- if (!conv.load (entry , convert)) {
156
+ if (!conv.load (it , convert)) {
80
157
return false ;
81
158
}
82
159
value.insert (cast_op<Key &&>(std::move (conv)));
83
160
}
84
161
return true ;
85
162
}
86
163
164
+ bool convert_anyset (anyset s, bool convert) {
165
+ value.clear ();
166
+ reserve_maybe (s, &value);
167
+ return convert_iterable (s, convert);
168
+ }
169
+
170
+ public:
171
+ bool load (handle src, bool convert) {
172
+ if (!PyObjectTypeIsConvertibleToStdSet (src.ptr ())) {
173
+ return false ;
174
+ }
175
+ if (isinstance<anyset>(src)) {
176
+ value.clear ();
177
+ return convert_anyset (reinterpret_borrow<anyset>(src), convert);
178
+ }
179
+ if (!convert) {
180
+ return false ;
181
+ }
182
+ assert (isinstance<iterable>(src));
183
+ value.clear ();
184
+ return convert_iterable (reinterpret_borrow<iterable>(src), convert);
185
+ }
186
+
87
187
template <typename T>
88
188
static handle cast (T &&src, return_value_policy policy, handle parent) {
89
189
if (!std::is_lvalue_reference<T>::value) {
@@ -115,15 +215,10 @@ struct map_caster {
115
215
}
116
216
void reserve_maybe (const dict &, void *) {}
117
217
118
- public:
119
- bool load (handle src, bool convert) {
120
- if (!isinstance<dict>(src)) {
121
- return false ;
122
- }
123
- auto d = reinterpret_borrow<dict>(src);
218
+ bool convert_elements (const dict &d, bool convert) {
124
219
value.clear ();
125
220
reserve_maybe (d, &value);
126
- for (auto it : d) {
221
+ for (const auto & it : d) {
127
222
key_conv kconv;
128
223
value_conv vconv;
129
224
if (!kconv.load (it.first .ptr (), convert) || !vconv.load (it.second .ptr (), convert)) {
@@ -134,6 +229,25 @@ struct map_caster {
134
229
return true ;
135
230
}
136
231
232
+ public:
233
+ bool load (handle src, bool convert) {
234
+ if (!PyObjectTypeIsConvertibleToStdMap (src.ptr ())) {
235
+ return false ;
236
+ }
237
+ if (isinstance<dict>(src)) {
238
+ return convert_elements (reinterpret_borrow<dict>(src), convert);
239
+ }
240
+ if (!convert) {
241
+ return false ;
242
+ }
243
+ auto items = reinterpret_steal<object>(PyMapping_Items (src.ptr ()));
244
+ if (!items) {
245
+ throw error_already_set ();
246
+ }
247
+ assert (isinstance<iterable>(items));
248
+ return convert_elements (dict (reinterpret_borrow<iterable>(items)), convert);
249
+ }
250
+
137
251
template <typename T>
138
252
static handle cast (T &&src, return_value_policy policy, handle parent) {
139
253
dict d;
@@ -166,13 +280,35 @@ struct list_caster {
166
280
using value_conv = make_caster<Value>;
167
281
168
282
bool load (handle src, bool convert) {
169
- if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
283
+ if (!PyObjectTypeIsConvertibleToStdVector (src.ptr ())) {
284
+ return false ;
285
+ }
286
+ if (isinstance<sequence>(src)) {
287
+ return convert_elements (src, convert);
288
+ }
289
+ if (!convert) {
170
290
return false ;
171
291
}
172
- auto s = reinterpret_borrow<sequence>(src);
292
+ // Designed to be behavior-equivalent to passing tuple(src) from Python:
293
+ // The conversion to a tuple will first exhaust the generator object, to ensure that
294
+ // the generator is not left in an unpredictable (to the caller) partially-consumed
295
+ // state.
296
+ assert (isinstance<iterable>(src));
297
+ return convert_elements (tuple (reinterpret_borrow<iterable>(src)), convert);
298
+ }
299
+
300
+ private:
301
+ template <typename T = Type, enable_if_t <has_reserve_method<T>::value, int > = 0 >
302
+ void reserve_maybe (const sequence &s, Type *) {
303
+ value.reserve (s.size ());
304
+ }
305
+ void reserve_maybe (const sequence &, void *) {}
306
+
307
+ bool convert_elements (handle seq, bool convert) {
308
+ auto s = reinterpret_borrow<sequence>(seq);
173
309
value.clear ();
174
310
reserve_maybe (s, &value);
175
- for (const auto &it : s ) {
311
+ for (const auto &it : seq ) {
176
312
value_conv conv;
177
313
if (!conv.load (it, convert)) {
178
314
return false ;
@@ -182,13 +318,6 @@ struct list_caster {
182
318
return true ;
183
319
}
184
320
185
- private:
186
- template <typename T = Type, enable_if_t <has_reserve_method<T>::value, int > = 0 >
187
- void reserve_maybe (const sequence &s, Type *) {
188
- value.reserve (s.size ());
189
- }
190
- void reserve_maybe (const sequence &, void *) {}
191
-
192
321
public:
193
322
template <typename T>
194
323
static handle cast (T &&src, return_value_policy policy, handle parent) {
@@ -237,12 +366,8 @@ struct array_caster {
237
366
return size == Size;
238
367
}
239
368
240
- public:
241
- bool load (handle src, bool convert) {
242
- if (!isinstance<sequence>(src)) {
243
- return false ;
244
- }
245
- auto l = reinterpret_borrow<sequence>(src);
369
+ bool convert_elements (handle seq, bool convert) {
370
+ auto l = reinterpret_borrow<sequence>(seq);
246
371
if (!require_size (l.size ())) {
247
372
return false ;
248
373
}
@@ -257,6 +382,25 @@ struct array_caster {
257
382
return true ;
258
383
}
259
384
385
+ public:
386
+ bool load (handle src, bool convert) {
387
+ if (!PyObjectTypeIsConvertibleToStdVector (src.ptr ())) {
388
+ return false ;
389
+ }
390
+ if (isinstance<sequence>(src)) {
391
+ return convert_elements (src, convert);
392
+ }
393
+ if (!convert) {
394
+ return false ;
395
+ }
396
+ // Designed to be behavior-equivalent to passing tuple(src) from Python:
397
+ // The conversion to a tuple will first exhaust the generator object, to ensure that
398
+ // the generator is not left in an unpredictable (to the caller) partially-consumed
399
+ // state.
400
+ assert (isinstance<iterable>(src));
401
+ return convert_elements (tuple (reinterpret_borrow<iterable>(src)), convert);
402
+ }
403
+
260
404
template <typename T>
261
405
static handle cast (T &&src, return_value_policy policy, handle parent) {
262
406
list l (src.size ());
0 commit comments