Skip to content

Commit 7d30378

Browse files
committed
Change list_caster to also accept generator objects (PyGen_Check(src.ptr()).
Note for completeness: This is a more conservative change than google/pybind11clif#30042
1 parent d2a36d9 commit 7d30378

File tree

2 files changed

+39
-12
lines changed

2 files changed

+39
-12
lines changed

include/pybind11/stl.h

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,38 @@ struct list_caster {
166166
using value_conv = make_caster<Value>;
167167

168168
bool load(handle src, bool convert) {
169-
if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
169+
if (isinstance<bytes>(src) || isinstance<str>(src)) {
170170
return false;
171171
}
172-
auto s = reinterpret_borrow<sequence>(src);
172+
if (isinstance<sequence>(src)) {
173+
return convert_elements(src, convert);
174+
}
175+
if (!convert) {
176+
return false;
177+
}
178+
if (PyGen_Check(src.ptr())) {
179+
// Designed to be behavior-equivalent to passing tuple(src) from Python:
180+
// The conversion to a tuple will first exhaust the generator object, to ensure that
181+
// the generator is not left in an unpredictable (to the caller) partially-consumed
182+
// state.
183+
assert(isinstance<iterable>(src));
184+
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
185+
}
186+
return false;
187+
}
188+
189+
private:
190+
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
191+
void reserve_maybe(const sequence &s, Type *) {
192+
value.reserve(s.size());
193+
}
194+
void reserve_maybe(const sequence &, void *) {}
195+
196+
bool convert_elements(handle seq, bool convert) {
197+
auto s = reinterpret_borrow<sequence>(seq);
173198
value.clear();
174199
reserve_maybe(s, &value);
175-
for (auto it : s) {
200+
for (auto it : seq) {
176201
value_conv conv;
177202
if (!conv.load(it, convert)) {
178203
return false;
@@ -182,13 +207,6 @@ struct list_caster {
182207
return true;
183208
}
184209

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-
192210
public:
193211
template <typename T>
194212
static handle cast(T &&src, return_value_policy policy, handle parent) {

tests/test_stl.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,14 +385,13 @@ def test_pass_std_vector_int():
385385
fn = m.pass_std_vector_int
386386
assert fn([1, 2]) == 2
387387
assert fn((1, 2)) == 2
388+
assert fn(i for i in range(3)) == 3
388389
with pytest.raises(TypeError):
389390
fn(set())
390391
with pytest.raises(TypeError):
391392
fn({})
392393
with pytest.raises(TypeError):
393394
fn({}.keys())
394-
with pytest.raises(TypeError):
395-
fn(i for i in range(3))
396395

397396

398397
def test_pass_std_set_int():
@@ -408,3 +407,13 @@ def test_pass_std_set_int():
408407
fn({}.keys())
409408
with pytest.raises(TypeError):
410409
fn(i for i in range(3))
410+
411+
412+
def test_list_caster_fully_consumes_generator_object():
413+
def gen_mix():
414+
yield from [1, 2.0, 3]
415+
416+
gen_obj = gen_mix()
417+
with pytest.raises(TypeError):
418+
m.pass_std_vector_int(gen_obj)
419+
assert not tuple(gen_obj)

0 commit comments

Comments
 (0)