Skip to content

Commit 03ea91b

Browse files
committed
gh-158192: expose the Future in FutureIter as fi_future
I'd be happy to bikeshed about the name if anybody wants to, but the scheme kind of matches generators and similar. For consistency, I made the Python implementation match, which required a wrapper class. I can drop that if it doesn't seem worth the hassle; the future can be introspected from the frame object of the generator already.
1 parent a5b03fa commit 03ea91b

3 files changed

Lines changed: 48 additions & 1 deletion

File tree

‎Lib/asyncio/futures.py‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,17 +293,43 @@ def set_exception(self, exception):
293293
self.__schedule_callbacks()
294294
self.__log_traceback = True
295295

296-
def __await__(self):
296+
def _await(self):
297297
if not self.done():
298298
self._asyncio_future_blocking = True
299299
yield self # This tells Task to wait for completion.
300300
if not self.done():
301301
raise RuntimeError("await wasn't used with future")
302302
return self.result() # May raise too.
303303

304+
def __await__(self):
305+
return _FutureIter(self)
306+
304307
__iter__ = __await__ # make compatible with 'yield from'.
305308

306309

310+
class _FutureIter:
311+
def __init__(self, fut):
312+
# The whole point of this class is to expose fi_future, like the C
313+
# version
314+
self.fi_future = fut
315+
self.__gen = fut._await()
316+
317+
def __iter__(self):
318+
return self
319+
320+
def __next__(self):
321+
return self.__gen.__next__()
322+
323+
def send(self, v, /):
324+
return self.__gen.send(v)
325+
326+
def throw(self, *args):
327+
return self.__gen.throw(*args)
328+
329+
def close(self):
330+
self.__gen.close()
331+
332+
307333
# Needed for testing purposes.
308334
_PyFuture = Future
309335

‎Lib/test/test_asyncio/test_futures.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,10 @@ def test_future_stop_iteration_args(self):
712712
self.fail('StopIteration was expected')
713713
self.assertEqual(result, (1, 2))
714714

715+
def test_future_iter_fut(self):
716+
fut = self._new_future(loop=self.loop)
717+
self.assertIs(iter(fut).fi_future, fut)
718+
715719
def test_future_iter_throw(self):
716720
fut = self._new_future(loop=self.loop)
717721
fi = iter(fut)

‎Modules/_asynciomodule.c‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,22 @@ static PyMethodDef FutureIter_methods[] = {
19711971
{NULL, NULL} /* Sentinel */
19721972
};
19731973

1974+
static PyObject *
1975+
FutureIter_get_fi_future(PyObject *op, void *Py_UNUSED(closure))
1976+
{
1977+
futureiterobject *it = (futureiterobject*)op;
1978+
PyObject *fut;
1979+
Py_BEGIN_CRITICAL_SECTION(op);
1980+
fut = it->future ? Py_NewRef(it->future) : Py_None;
1981+
Py_END_CRITICAL_SECTION();
1982+
return fut;
1983+
}
1984+
1985+
static PyGetSetDef FutureIter_getsetlist[] = {
1986+
{"fi_future", FutureIter_get_fi_future, NULL, NULL},
1987+
{NULL} /* Sentinel */
1988+
};
1989+
19741990
static PyType_Slot FutureIter_slots[] = {
19751991
{Py_tp_dealloc, FutureIter_dealloc},
19761992
{Py_tp_getattro, PyObject_GenericGetAttr},
@@ -1979,6 +1995,7 @@ static PyType_Slot FutureIter_slots[] = {
19791995
{Py_tp_iter, PyObject_SelfIter},
19801996
{Py_tp_iternext, FutureIter_iternext},
19811997
{Py_tp_methods, FutureIter_methods},
1998+
{Py_tp_getset, FutureIter_getsetlist},
19821999

19832000
// async methods
19842001
{Py_am_send, FutureIter_am_send},

0 commit comments

Comments
 (0)