diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 31a7671f55f..153b58b4fbf 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -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 `. - .. versionchanged:: 3.5.2 - Starting with CPython 3.5.2, ``__aiter__`` can directly return - :term:`asynchronous iterators `. 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: diff --git a/Include/genobject.h b/Include/genobject.h index 8c1825fc070..b9db9f9c1c4 100644 --- a/Include/genobject.h +++ b/Include/genobject.h @@ -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); diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index a82cc79acaa..9fda8537686 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -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 diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 75defa12739..7e106affbe0 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -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 diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index ebd880bab0c..08035173d99 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -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 diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-10-06-00-27-04.bpo-31709._PmU51.rst b/Misc/NEWS.d/next/Core and Builtins/2017-10-06-00-27-04.bpo-31709._PmU51.rst new file mode 100644 index 00000000000..6c342eacae3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-10-06-00-27-04.bpo-31709._PmU51.rst @@ -0,0 +1 @@ +Drop support of asynchronous __aiter__. diff --git a/Objects/genobject.c b/Objects/genobject.c index 1a8c37abf23..5d5798c2f48 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -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 ========= */ diff --git a/Python/ceval.c b/Python/ceval.c index cf0c6c9ae2f..86ffec42b31 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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(); } diff --git a/Python/compile.c b/Python/compile.c index df5070aad2a..431e7531e84 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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); }