Initially (PEP 380), yield from
syntax was introduced to be used for delegating to a "subgenerator." Later it was used with now deprecated generator-based coroutines.
I cannot find out what kind of objects yield from
can be applied to in general. My first conjecture was that it only requires __iter__
method on the object to return an iterator. Indeed, the following works with Python 3.8:
class C:
def __init__(self, n):
self.n = n
def __iter__(self):
return iter(range(self.n))
def g(n):
yield from C(n)
print(tuple(g(3)))
However, it also works with some awaitables, like asyncio.sleep(1)
, which do not have __iter__
method.
What is the general rule? What determines if an object can be given as an argument to yield from
form?
In simpler words, the yield keyword will convert an expression that is specified along with it to a generator object and return it to the caller. Hence, if you want to get the values stored inside the generator object, you need to iterate over it. It will not destroy the local variables’ states.
When a yield method returns an IEnumerable type object, this object implements both IEnumerable and IEnumerator. Casting this object to IEnumerator produces a generator that is useless until the GetEnumerator method is called. At the same time, if a generator seems ‘dead’, it may suddenly start working after the GetEnumerator method call.
When you use a yield keyword inside a generator function, it returns a generator object instead of values. In fact, it stores all the returned values inside this generator object in a local state. If you have used the return statement, which returned an array of values, this would have consumed a lot of memory.
Methods that use yield can really simplify your life sometimes. Behind this magic exists an entire class the compiler generated, which is why I recommend you use the yield feature only when it is significantly more convenient that, for example, LINQ.
You can check how CPython evaluates that statement. From this follows it needs to be either a coroutine or an iterable:
case TARGET(GET_YIELD_FROM_ITER): {
/* before: [obj]; after [getiter(obj)] */
PyObject *iterable = TOP();
PyObject *iter;
if (PyCoro_CheckExact(iterable)) {
/* `iterable` is a coroutine */
if (!(co->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE))) {
/* and it is used in a 'yield from' expression of a
regular generator. */
Py_DECREF(iterable);
SET_TOP(NULL);
_PyErr_SetString(tstate, PyExc_TypeError,
"cannot 'yield from' a coroutine object "
"in a non-coroutine generator");
goto error;
}
}
else if (!PyGen_CheckExact(iterable)) {
/* `iterable` is not a generator. */
iter = PyObject_GetIter(iterable);
Py_DECREF(iterable);
SET_TOP(iter);
if (iter == NULL)
goto error;
}
PREDICT(LOAD_CONST);
DISPATCH();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With