Check the source code of the set.update()
:
static int
set_update_internal(PySetObject *so, PyObject *other)
{
PyObject *key, *it;
/* ... fast path for dict and set ... */
it = PyObject_GetIter(other);
if (it == NULL)
return -1;
while ((key = PyIter_Next(it)) != NULL) {
if (set_add_key(so, key)) {
Py_DECREF(it);
Py_DECREF(key);
return -1;
}
Py_DECREF(key);
}
Py_DECREF(it);
if (PyErr_Occurred())
return -1;
return 0;
}
static PyObject *
set_update(PySetObject *so, PyObject *args)
{
Py_ssize_t i;
for (i=0 ; i<PyTuple_GET_SIZE(args) ; i++) {
PyObject *other = PyTuple_GET_ITEM(args, i);
if (set_update_internal(so, other))
return NULL;
}
Py_RETURN_NONE;
}
I noticed that at the end of the set_update_internal()
, the code only checked if PyErr_Occurred()
was NULL
to return the error code, and did not do any special handling for the StopIteration
exception.
In my supposition, if the iterator runs out and raises a StopIteration
, set.update()
will end prematurely or even allow the exception to continue backtracking.
But the fact is not so, set.update()
works properly:
class Foo:
def __iter__(self):
return self
def __next__(self):
raise StopIteration
>>> s = set()
>>> s.update(Foo(), range(10))
>>> s
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
If there is other exception, set.update()
will be terminated as expected.
This looks like PyErr_Occurred()
returned NULL
when StopIteration
was raised. I want to know if this is indeed the case and why it is.
I have checked the source code of the slot_tp_iternext()
, and it appears that it is just a simple call to the __next__
method without handling StopIteration
exception:
static PyObject *
slot_tp_iternext(PyObject *self)
{
PyObject *stack[1] = {self};
return vectorcall_method(&_Py_ID(__next__), stack, 1);
}
There seems to be no special handling of StopIteration
in the bytecode:
TARGET(RAISE_VARARGS) {
PyObject *cause = NULL, *exc = NULL;
switch (oparg) {
case 2:
cause = POP(); /* cause */
/* fall through */
case 1:
exc = POP(); /* exc */
/* fall through */
case 0:
if (do_raise(tstate, exc, cause)) {
goto exception_unwind;
}
break;
default:
_PyErr_SetString(tstate, PyExc_SystemError,
"bad RAISE_VARARGS oparg");
break;
}
goto error;
}