bpo-31709: Drop support for asynchronous __aiter__. (#3903)
This commit is contained in:
parent
86566702f3
commit
faa135acbf
|
@ -2520,9 +2520,8 @@ generators, coroutines do not directly support iteration.
|
|||
Asynchronous Iterators
|
||||
----------------------
|
||||
|
||||
An *asynchronous iterable* is able to call asynchronous code in its
|
||||
``__aiter__`` implementation, and an *asynchronous iterator* can call
|
||||
asynchronous code in its ``__anext__`` method.
|
||||
An *asynchronous iterator* can call asynchronous code in
|
||||
its ``__anext__`` method.
|
||||
|
||||
Asynchronous iterators can be used in an :keyword:`async for` statement.
|
||||
|
||||
|
@ -2552,48 +2551,14 @@ An example of an asynchronous iterable object::
|
|||
|
||||
.. versionadded:: 3.5
|
||||
|
||||
.. note::
|
||||
.. versionchanged:: 3.7
|
||||
Prior to Python 3.7, ``__aiter__`` could return an *awaitable*
|
||||
that would resolve to an
|
||||
:term:`asynchronous iterator <asynchronous iterator>`.
|
||||
|
||||
.. versionchanged:: 3.5.2
|
||||
Starting with CPython 3.5.2, ``__aiter__`` can directly return
|
||||
:term:`asynchronous iterators <asynchronous iterator>`. Returning
|
||||
an :term:`awaitable` object will result in a
|
||||
:exc:`PendingDeprecationWarning`.
|
||||
|
||||
The recommended way of writing backwards compatible code in
|
||||
CPython 3.5.x is to continue returning awaitables from
|
||||
``__aiter__``. If you want to avoid the PendingDeprecationWarning
|
||||
and keep the code backwards compatible, the following decorator
|
||||
can be used::
|
||||
|
||||
import functools
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 5, 2):
|
||||
def aiter_compat(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(self):
|
||||
return func(self)
|
||||
return wrapper
|
||||
else:
|
||||
def aiter_compat(func):
|
||||
return func
|
||||
|
||||
Example::
|
||||
|
||||
class AsyncIterator:
|
||||
|
||||
@aiter_compat
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
...
|
||||
|
||||
Starting with CPython 3.6, the :exc:`PendingDeprecationWarning`
|
||||
will be replaced with the :exc:`DeprecationWarning`.
|
||||
In CPython 3.7, returning an awaitable from ``__aiter__`` will
|
||||
result in a :exc:`RuntimeError`.
|
||||
Starting with Python 3.7, ``__aiter__`` must return an
|
||||
asynchronous iterator object. Returning anything else
|
||||
will result in a :exc:`TypeError` error.
|
||||
|
||||
|
||||
.. _async-context-managers:
|
||||
|
|
|
@ -56,7 +56,6 @@ PyAPI_DATA(PyTypeObject) PyCoro_Type;
|
|||
PyAPI_DATA(PyTypeObject) _PyCoroWrapper_Type;
|
||||
|
||||
PyAPI_DATA(PyTypeObject) _PyAIterWrapper_Type;
|
||||
PyObject *_PyAIterWrapper_New(PyObject *aiter);
|
||||
|
||||
#define PyCoro_CheckExact(op) (Py_TYPE(op) == &PyCoro_Type)
|
||||
PyObject *_PyCoro_GetAwaitableIter(PyObject *o);
|
||||
|
|
|
@ -676,20 +676,12 @@ class StreamReader:
|
|||
self._maybe_resume_transport()
|
||||
return data
|
||||
|
||||
if compat.PY35:
|
||||
@coroutine
|
||||
def __aiter__(self):
|
||||
return self
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
@coroutine
|
||||
def __anext__(self):
|
||||
val = yield from self.readline()
|
||||
if val == b'':
|
||||
raise StopAsyncIteration
|
||||
return val
|
||||
|
||||
if compat.PY352:
|
||||
# In Python 3.5.2 and greater, __aiter__ should return
|
||||
# the asynchronous iterator directly.
|
||||
def __aiter__(self):
|
||||
return self
|
||||
@coroutine
|
||||
def __anext__(self):
|
||||
val = yield from self.readline()
|
||||
if val == b'':
|
||||
raise StopAsyncIteration
|
||||
return val
|
||||
|
|
|
@ -660,7 +660,7 @@ class TestOneTrickPonyABCs(ABCTestCase):
|
|||
|
||||
def test_AsyncIterable(self):
|
||||
class AI:
|
||||
async def __aiter__(self):
|
||||
def __aiter__(self):
|
||||
return self
|
||||
self.assertTrue(isinstance(AI(), AsyncIterable))
|
||||
self.assertTrue(issubclass(AI, AsyncIterable))
|
||||
|
@ -674,7 +674,7 @@ class TestOneTrickPonyABCs(ABCTestCase):
|
|||
|
||||
def test_AsyncIterator(self):
|
||||
class AI:
|
||||
async def __aiter__(self):
|
||||
def __aiter__(self):
|
||||
return self
|
||||
async def __anext__(self):
|
||||
raise StopAsyncIteration
|
||||
|
|
|
@ -1382,7 +1382,7 @@ class CoroutineTest(unittest.TestCase):
|
|||
def __init__(self):
|
||||
self.i = 0
|
||||
|
||||
async def __aiter__(self):
|
||||
def __aiter__(self):
|
||||
nonlocal aiter_calls
|
||||
aiter_calls += 1
|
||||
return self
|
||||
|
@ -1401,9 +1401,8 @@ class CoroutineTest(unittest.TestCase):
|
|||
|
||||
buffer = []
|
||||
async def test1():
|
||||
with self.assertWarnsRegex(DeprecationWarning, "legacy"):
|
||||
async for i1, i2 in AsyncIter():
|
||||
buffer.append(i1 + i2)
|
||||
async for i1, i2 in AsyncIter():
|
||||
buffer.append(i1 + i2)
|
||||
|
||||
yielded, _ = run_async(test1())
|
||||
# Make sure that __aiter__ was called only once
|
||||
|
@ -1415,13 +1414,12 @@ class CoroutineTest(unittest.TestCase):
|
|||
buffer = []
|
||||
async def test2():
|
||||
nonlocal buffer
|
||||
with self.assertWarnsRegex(DeprecationWarning, "legacy"):
|
||||
async for i in AsyncIter():
|
||||
buffer.append(i[0])
|
||||
if i[0] == 20:
|
||||
break
|
||||
else:
|
||||
buffer.append('what?')
|
||||
async for i in AsyncIter():
|
||||
buffer.append(i[0])
|
||||
if i[0] == 20:
|
||||
break
|
||||
else:
|
||||
buffer.append('what?')
|
||||
buffer.append('end')
|
||||
|
||||
yielded, _ = run_async(test2())
|
||||
|
@ -1434,13 +1432,12 @@ class CoroutineTest(unittest.TestCase):
|
|||
buffer = []
|
||||
async def test3():
|
||||
nonlocal buffer
|
||||
with self.assertWarnsRegex(DeprecationWarning, "legacy"):
|
||||
async for i in AsyncIter():
|
||||
if i[0] > 20:
|
||||
continue
|
||||
buffer.append(i[0])
|
||||
else:
|
||||
buffer.append('what?')
|
||||
async for i in AsyncIter():
|
||||
if i[0] > 20:
|
||||
continue
|
||||
buffer.append(i[0])
|
||||
else:
|
||||
buffer.append('what?')
|
||||
buffer.append('end')
|
||||
|
||||
yielded, _ = run_async(test3())
|
||||
|
@ -1479,7 +1476,7 @@ class CoroutineTest(unittest.TestCase):
|
|||
|
||||
with self.assertRaisesRegex(
|
||||
TypeError,
|
||||
r"async for' received an invalid object.*__aiter.*\: I"):
|
||||
r"that does not implement __anext__"):
|
||||
|
||||
run_async(foo())
|
||||
|
||||
|
@ -1508,25 +1505,6 @@ class CoroutineTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(sys.getrefcount(aiter), refs_before)
|
||||
|
||||
def test_for_5(self):
|
||||
class I:
|
||||
async def __aiter__(self):
|
||||
return self
|
||||
|
||||
def __anext__(self):
|
||||
return 123
|
||||
|
||||
async def foo():
|
||||
with self.assertWarnsRegex(DeprecationWarning, "legacy"):
|
||||
async for i in I():
|
||||
print('never going to happen')
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
TypeError,
|
||||
"async for' received an invalid object.*__anext.*int"):
|
||||
|
||||
run_async(foo())
|
||||
|
||||
def test_for_6(self):
|
||||
I = 0
|
||||
|
||||
|
@ -1622,13 +1600,12 @@ class CoroutineTest(unittest.TestCase):
|
|||
def test_for_7(self):
|
||||
CNT = 0
|
||||
class AI:
|
||||
async def __aiter__(self):
|
||||
def __aiter__(self):
|
||||
1/0
|
||||
async def foo():
|
||||
nonlocal CNT
|
||||
with self.assertWarnsRegex(DeprecationWarning, "legacy"):
|
||||
async for i in AI():
|
||||
CNT += 1
|
||||
async for i in AI():
|
||||
CNT += 1
|
||||
CNT += 10
|
||||
with self.assertRaises(ZeroDivisionError):
|
||||
run_async(foo())
|
||||
|
@ -1652,37 +1629,6 @@ class CoroutineTest(unittest.TestCase):
|
|||
run_async(foo())
|
||||
self.assertEqual(CNT, 0)
|
||||
|
||||
def test_for_9(self):
|
||||
# Test that DeprecationWarning can safely be converted into
|
||||
# an exception (__aiter__ should not have a chance to raise
|
||||
# a ZeroDivisionError.)
|
||||
class AI:
|
||||
async def __aiter__(self):
|
||||
1/0
|
||||
async def foo():
|
||||
async for i in AI():
|
||||
pass
|
||||
|
||||
with self.assertRaises(DeprecationWarning):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
run_async(foo())
|
||||
|
||||
def test_for_10(self):
|
||||
# Test that DeprecationWarning can safely be converted into
|
||||
# an exception.
|
||||
class AI:
|
||||
async def __aiter__(self):
|
||||
pass
|
||||
async def foo():
|
||||
async for i in AI():
|
||||
pass
|
||||
|
||||
with self.assertRaises(DeprecationWarning):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
run_async(foo())
|
||||
|
||||
def test_for_11(self):
|
||||
class F:
|
||||
def __aiter__(self):
|
||||
|
@ -1703,24 +1649,6 @@ class CoroutineTest(unittest.TestCase):
|
|||
err = c.exception
|
||||
self.assertIsInstance(err.__cause__, ZeroDivisionError)
|
||||
|
||||
def test_for_12(self):
|
||||
class F:
|
||||
def __aiter__(self):
|
||||
return self
|
||||
def __await__(self):
|
||||
1 / 0
|
||||
|
||||
async def main():
|
||||
async for _ in F():
|
||||
pass
|
||||
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
'an invalid object from __aiter__') as c:
|
||||
main().send(None)
|
||||
|
||||
err = c.exception
|
||||
self.assertIsInstance(err.__cause__, ZeroDivisionError)
|
||||
|
||||
def test_for_tuple(self):
|
||||
class Done(Exception): pass
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Drop support of asynchronous __aiter__.
|
|
@ -1141,100 +1141,6 @@ PyCoro_New(PyFrameObject *f, PyObject *name, PyObject *qualname)
|
|||
}
|
||||
|
||||
|
||||
/* __aiter__ wrapper; see http://bugs.python.org/issue27243 for details. */
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
PyObject *ags_aiter;
|
||||
} PyAIterWrapper;
|
||||
|
||||
|
||||
static PyObject *
|
||||
aiter_wrapper_iternext(PyAIterWrapper *aw)
|
||||
{
|
||||
_PyGen_SetStopIterationValue(aw->ags_aiter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
aiter_wrapper_traverse(PyAIterWrapper *aw, visitproc visit, void *arg)
|
||||
{
|
||||
Py_VISIT((PyObject *)aw->ags_aiter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
aiter_wrapper_dealloc(PyAIterWrapper *aw)
|
||||
{
|
||||
_PyObject_GC_UNTRACK((PyObject *)aw);
|
||||
Py_CLEAR(aw->ags_aiter);
|
||||
PyObject_GC_Del(aw);
|
||||
}
|
||||
|
||||
static PyAsyncMethods aiter_wrapper_as_async = {
|
||||
PyObject_SelfIter, /* am_await */
|
||||
0, /* am_aiter */
|
||||
0 /* am_anext */
|
||||
};
|
||||
|
||||
PyTypeObject _PyAIterWrapper_Type = {
|
||||
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||
"aiter_wrapper",
|
||||
sizeof(PyAIterWrapper), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
(destructor)aiter_wrapper_dealloc, /* destructor tp_dealloc */
|
||||
0, /* tp_print */
|
||||
0, /* tp_getattr */
|
||||
0, /* tp_setattr */
|
||||
&aiter_wrapper_as_async, /* tp_as_async */
|
||||
0, /* tp_repr */
|
||||
0, /* tp_as_number */
|
||||
0, /* tp_as_sequence */
|
||||
0, /* tp_as_mapping */
|
||||
0, /* tp_hash */
|
||||
0, /* tp_call */
|
||||
0, /* tp_str */
|
||||
PyObject_GenericGetAttr, /* tp_getattro */
|
||||
0, /* tp_setattro */
|
||||
0, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
|
||||
"A wrapper object for __aiter__ bakwards compatibility.",
|
||||
(traverseproc)aiter_wrapper_traverse, /* tp_traverse */
|
||||
0, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
PyObject_SelfIter, /* tp_iter */
|
||||
(iternextfunc)aiter_wrapper_iternext, /* tp_iternext */
|
||||
0, /* tp_methods */
|
||||
0, /* tp_members */
|
||||
0, /* tp_getset */
|
||||
0, /* tp_base */
|
||||
0, /* tp_dict */
|
||||
0, /* tp_descr_get */
|
||||
0, /* tp_descr_set */
|
||||
0, /* tp_dictoffset */
|
||||
0, /* tp_init */
|
||||
0, /* tp_alloc */
|
||||
0, /* tp_new */
|
||||
0, /* tp_free */
|
||||
};
|
||||
|
||||
|
||||
PyObject *
|
||||
_PyAIterWrapper_New(PyObject *aiter)
|
||||
{
|
||||
PyAIterWrapper *aw = PyObject_GC_New(PyAIterWrapper,
|
||||
&_PyAIterWrapper_Type);
|
||||
if (aw == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(aiter);
|
||||
aw->ags_aiter = aiter;
|
||||
_PyObject_GC_TRACK(aw);
|
||||
return (PyObject *)aw;
|
||||
}
|
||||
|
||||
|
||||
/* ========= Asynchronous Generators ========= */
|
||||
|
||||
|
||||
|
|
|
@ -1708,7 +1708,6 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
|
|||
TARGET(GET_AITER) {
|
||||
unaryfunc getter = NULL;
|
||||
PyObject *iter = NULL;
|
||||
PyObject *awaitable = NULL;
|
||||
PyObject *obj = TOP();
|
||||
PyTypeObject *type = Py_TYPE(obj);
|
||||
|
||||
|
@ -1735,57 +1734,20 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (Py_TYPE(iter)->tp_as_async != NULL &&
|
||||
Py_TYPE(iter)->tp_as_async->am_anext != NULL) {
|
||||
|
||||
/* Starting with CPython 3.5.2 __aiter__ should return
|
||||
asynchronous iterators directly (not awaitables that
|
||||
resolve to asynchronous iterators.)
|
||||
|
||||
Therefore, we check if the object that was returned
|
||||
from __aiter__ has an __anext__ method. If it does,
|
||||
we wrap it in an awaitable that resolves to `iter`.
|
||||
|
||||
See http://bugs.python.org/issue27243 for more
|
||||
details.
|
||||
*/
|
||||
|
||||
PyObject *wrapper = _PyAIterWrapper_New(iter);
|
||||
Py_DECREF(iter);
|
||||
SET_TOP(wrapper);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
awaitable = _PyCoro_GetAwaitableIter(iter);
|
||||
if (awaitable == NULL) {
|
||||
_PyErr_FormatFromCause(
|
||||
PyExc_TypeError,
|
||||
"'async for' received an invalid object "
|
||||
"from __aiter__: %.100s",
|
||||
Py_TYPE(iter)->tp_name);
|
||||
if (Py_TYPE(iter)->tp_as_async == NULL ||
|
||||
Py_TYPE(iter)->tp_as_async->am_anext == NULL) {
|
||||
|
||||
SET_TOP(NULL);
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"'async for' received an object from __aiter__ "
|
||||
"that does not implement __anext__: %.100s",
|
||||
Py_TYPE(iter)->tp_name);
|
||||
Py_DECREF(iter);
|
||||
goto error;
|
||||
} else {
|
||||
Py_DECREF(iter);
|
||||
|
||||
if (PyErr_WarnFormat(
|
||||
PyExc_DeprecationWarning, 1,
|
||||
"'%.100s' implements legacy __aiter__ protocol; "
|
||||
"__aiter__ should return an asynchronous "
|
||||
"iterator, not awaitable",
|
||||
type->tp_name))
|
||||
{
|
||||
/* Warning was converted to an error. */
|
||||
Py_DECREF(awaitable);
|
||||
SET_TOP(NULL);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
SET_TOP(awaitable);
|
||||
PREDICT(LOAD_CONST);
|
||||
SET_TOP(iter);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
|
|
@ -2298,8 +2298,6 @@ compiler_async_for(struct compiler *c, stmt_ty s)
|
|||
|
||||
VISIT(c, expr, s->v.AsyncFor.iter);
|
||||
ADDOP(c, GET_AITER);
|
||||
ADDOP_O(c, LOAD_CONST, Py_None, consts);
|
||||
ADDOP(c, YIELD_FROM);
|
||||
|
||||
compiler_use_next_block(c, try);
|
||||
|
||||
|
@ -3867,8 +3865,6 @@ compiler_async_comprehension_generator(struct compiler *c,
|
|||
/* Sub-iter - calculate on the fly */
|
||||
VISIT(c, expr, gen->iter);
|
||||
ADDOP(c, GET_AITER);
|
||||
ADDOP_O(c, LOAD_CONST, Py_None, consts);
|
||||
ADDOP(c, YIELD_FROM);
|
||||
}
|
||||
|
||||
compiler_use_next_block(c, try);
|
||||
|
@ -4033,8 +4029,6 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
|
|||
|
||||
if (outermost->is_async) {
|
||||
ADDOP(c, GET_AITER);
|
||||
ADDOP_O(c, LOAD_CONST, Py_None, consts);
|
||||
ADDOP(c, YIELD_FROM);
|
||||
} else {
|
||||
ADDOP(c, GET_ITER);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue