@@ -43,7 +43,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
43
43
// Begin: Equivalent of
44
44
// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438
45
45
/*
46
- The three `PyObjectTypeIsConvertibleTo *()` functions below are
46
+ The three `object_is_convertible_to_ *()` functions below are
47
47
the result of converging the behaviors of pybind11 and PyCLIF
48
48
(http://github.com/google/clif).
49
49
@@ -69,10 +69,13 @@ to prevent accidents and improve readability:
69
69
are also fairly commonly used, therefore enforcing explicit conversions
70
70
would have an unfavorable cost : benefit ratio; more sloppily speaking,
71
71
such an enforcement would be more annoying than helpful.
72
+
73
+ Additional checks have been added to allow types derived from `collections.abc.Set` and
74
+ `collections.abc.Mapping` (`collections.abc.Sequence` is already allowed by `PySequence_Check`).
72
75
*/
73
76
74
- inline bool PyObjectIsInstanceWithOneOfTpNames (PyObject *obj,
75
- std::initializer_list<const char *> tp_names) {
77
+ inline bool object_is_instance_with_one_of_tp_names (PyObject *obj,
78
+ std::initializer_list<const char *> tp_names) {
76
79
if (PyType_Check (obj)) {
77
80
return false ;
78
81
}
@@ -85,37 +88,48 @@ inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj,
85
88
return false ;
86
89
}
87
90
88
- inline bool PyObjectTypeIsConvertibleToStdVector (PyObject *obj) {
89
- if (PySequence_Check (obj) != 0 ) {
90
- return !PyUnicode_Check (obj) && !PyBytes_Check (obj);
91
+ inline bool object_is_convertible_to_std_vector (const handle &src) {
92
+ // Allow sequence-like objects, but not (byte-)string-like objects.
93
+ if (PySequence_Check (src.ptr ()) != 0 ) {
94
+ return !PyUnicode_Check (src.ptr ()) && !PyBytes_Check (src.ptr ());
91
95
}
92
- return (PyGen_Check (obj) != 0 ) || (PyAnySet_Check (obj) != 0 )
93
- || PyObjectIsInstanceWithOneOfTpNames (
94
- obj, {" dict_keys" , " dict_values" , " dict_items" , " map" , " zip" });
96
+ // Allow generators, set/frozenset and several common iterable types.
97
+ return (PyGen_Check (src.ptr ()) != 0 ) || (PyAnySet_Check (src.ptr ()) != 0 )
98
+ || object_is_instance_with_one_of_tp_names (
99
+ src.ptr (), {" dict_keys" , " dict_values" , " dict_items" , " map" , " zip" });
95
100
}
96
101
97
- inline bool PyObjectTypeIsConvertibleToStdSet (PyObject *obj) {
98
- return (PyAnySet_Check (obj) != 0 ) || PyObjectIsInstanceWithOneOfTpNames (obj, {" dict_keys" });
102
+ inline bool object_is_convertible_to_std_set (const handle &src, bool convert) {
103
+ // Allow set/frozenset and dict keys.
104
+ // In convert mode: also allow types derived from collections.abc.Set.
105
+ return ((PyAnySet_Check (src.ptr ()) != 0 )
106
+ || object_is_instance_with_one_of_tp_names (src.ptr (), {" dict_keys" }))
107
+ || (convert && isinstance (src, module_::import (" collections.abc" ).attr (" Set" )));
99
108
}
100
109
101
- inline bool PyObjectTypeIsConvertibleToStdMap (PyObject *obj) {
102
- if (PyDict_Check (obj)) {
110
+ inline bool object_is_convertible_to_std_map (const handle &src, bool convert) {
111
+ // Allow dict.
112
+ if (PyDict_Check (src.ptr ())) {
103
113
return true ;
104
114
}
105
- // Implicit requirement in the conditions below:
106
- // A type with `.__getitem__()` & `.items()` methods must implement these
107
- // to be compatible with https://docs.python.org/3/c-api/mapping.html
108
- if (PyMapping_Check (obj) == 0 ) {
109
- return false ;
110
- }
111
- PyObject *items = PyObject_GetAttrString (obj, " items" );
112
- if (items == nullptr ) {
113
- PyErr_Clear ();
114
- return false ;
115
+ // Allow types conforming to Mapping Protocol.
116
+ // According to https://docs.python.org/3/c-api/mapping.html, `PyMappingCheck()` checks for
117
+ // `__getitem__()` without checking the type of keys. In order to restrict the allowed types
118
+ // closer to actual Mapping-like types, we also check for the `items()` method.
119
+ if (PyMapping_Check (src.ptr ()) != 0 ) {
120
+ PyObject *items = PyObject_GetAttrString (src.ptr (), " items" );
121
+ if (items != nullptr ) {
122
+ bool is_convertible = (PyCallable_Check (items) != 0 );
123
+ Py_DECREF (items);
124
+ if (is_convertible) {
125
+ return true ;
126
+ }
127
+ } else {
128
+ PyErr_Clear ();
129
+ }
115
130
}
116
- bool is_convertible = (PyCallable_Check (items) != 0 );
117
- Py_DECREF (items);
118
- return is_convertible;
131
+ // In convert mode: Allow types derived from collections.abc.Mapping
132
+ return convert && isinstance (src, module_::import (" collections.abc" ).attr (" Mapping" ));
119
133
}
120
134
121
135
//
@@ -172,7 +186,7 @@ struct set_caster {
172
186
173
187
public:
174
188
bool load (handle src, bool convert) {
175
- if (!PyObjectTypeIsConvertibleToStdSet (src. ptr () )) {
189
+ if (!object_is_convertible_to_std_set (src, convert )) {
176
190
return false ;
177
191
}
178
192
if (isinstance<anyset>(src)) {
@@ -203,7 +217,9 @@ struct set_caster {
203
217
return s.release ();
204
218
}
205
219
206
- PYBIND11_TYPE_CASTER (type, const_name(" set[" ) + key_conv::name + const_name(" ]" ));
220
+ PYBIND11_TYPE_CASTER (type,
221
+ io_name (" collections.abc.Set" , " set" ) + const_name(" [" ) + key_conv::name
222
+ + const_name(" ]" ));
207
223
};
208
224
209
225
template <typename Type, typename Key, typename Value>
@@ -234,7 +250,7 @@ struct map_caster {
234
250
235
251
public:
236
252
bool load (handle src, bool convert) {
237
- if (!PyObjectTypeIsConvertibleToStdMap (src. ptr () )) {
253
+ if (!object_is_convertible_to_std_map (src, convert )) {
238
254
return false ;
239
255
}
240
256
if (isinstance<dict>(src)) {
@@ -274,7 +290,8 @@ struct map_caster {
274
290
}
275
291
276
292
PYBIND11_TYPE_CASTER (Type,
277
- const_name (" dict[" ) + key_conv::name + const_name(" , " ) + value_conv::name
293
+ io_name (" collections.abc.Mapping" , " dict" ) + const_name(" [" )
294
+ + key_conv::name + const_name(" , " ) + value_conv::name
278
295
+ const_name(" ]" ));
279
296
};
280
297
@@ -283,7 +300,7 @@ struct list_caster {
283
300
using value_conv = make_caster<Value>;
284
301
285
302
bool load (handle src, bool convert) {
286
- if (!PyObjectTypeIsConvertibleToStdVector (src. ptr () )) {
303
+ if (!object_is_convertible_to_std_vector (src)) {
287
304
return false ;
288
305
}
289
306
if (isinstance<sequence>(src)) {
@@ -340,7 +357,9 @@ struct list_caster {
340
357
return l.release ();
341
358
}
342
359
343
- PYBIND11_TYPE_CASTER (Type, const_name(" list[" ) + value_conv::name + const_name(" ]" ));
360
+ PYBIND11_TYPE_CASTER (Type,
361
+ io_name (" collections.abc.Sequence" , " list" ) + const_name(" [" )
362
+ + value_conv::name + const_name(" ]" ));
344
363
};
345
364
346
365
template <typename Type, typename Alloc>
@@ -416,7 +435,7 @@ struct array_caster {
416
435
417
436
public:
418
437
bool load (handle src, bool convert) {
419
- if (!PyObjectTypeIsConvertibleToStdVector (src. ptr () )) {
438
+ if (!object_is_convertible_to_std_vector (src)) {
420
439
return false ;
421
440
}
422
441
if (isinstance<sequence>(src)) {
@@ -474,10 +493,12 @@ struct array_caster {
474
493
using cast_op_type = movable_cast_op_type<T_>;
475
494
476
495
static constexpr auto name
477
- = const_name<Resizable>(const_name(" " ), const_name(" Annotated[" )) + const_name(" list[" )
478
- + value_conv::name + const_name(" ]" )
479
- + const_name<Resizable>(
480
- const_name (" " ), const_name(" , FixedSize(" ) + const_name<Size >() + const_name(" )]" ));
496
+ = const_name<Resizable>(const_name(" " ), const_name(" typing.Annotated[" ))
497
+ + io_name(" collections.abc.Sequence" , " list" ) + const_name(" [" ) + value_conv::name
498
+ + const_name(" ]" )
499
+ + const_name<Resizable>(const_name(" " ),
500
+ const_name (" , \" FixedSize(" ) + const_name<Size >()
501
+ + const_name(" )\" ]" ));
481
502
};
482
503
483
504
template <typename Type, size_t Size >
0 commit comments