[Python-checkins] bpo-31709: Drop support for asynchronous __aiter__. (#3903)

Yury Selivanov webhook-mailer at python.org
Fri Oct 6 02:09:00 EDT 2017


https://github.com/python/cpython/commit/faa135acbfcd55f79fb97f7525c8aa6f5a5b6a22
commit: faa135acbfcd55f79fb97f7525c8aa6f5a5b6a22
branch: master
author: Yury Selivanov <yury at magic.io>
committer: GitHub <noreply at github.com>
date: 2017-10-06T02:08:57-04:00
summary:

bpo-31709: Drop support for asynchronous __aiter__. (#3903)

files:
A Misc/NEWS.d/next/Core and Builtins/2017-10-06-00-27-04.bpo-31709._PmU51.rst
M Doc/reference/datamodel.rst
M Include/genobject.h
M Lib/asyncio/streams.py
M Lib/test/test_collections.py
M Lib/test/test_coroutines.py
M Objects/genobject.c
M Python/ceval.c
M Python/compile.c

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 <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:
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 @@ def readexactly(self, n):
         self._maybe_resume_transport()
         return data
 
-    if compat.PY35:
-        @coroutine
-        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
+    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 @@ def __hash__(self):
 
     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 AI:
 
     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 AsyncIter:
             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 @@ def __init__(self):
 
         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 @@ def __init__(self):
         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 @@ def __init__(self):
         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 @@ def __aiter__(self):
 
         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 @@ def __anext__(self):
 
         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 @@ def __aiter__(self):
     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 @@ def __aiter__(self):
                 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 @@ def __await__(self):
         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`.
+            if (Py_TYPE(iter)->tp_as_async == NULL ||
+                    Py_TYPE(iter)->tp_as_async->am_anext == NULL) {
 
-                   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(
+                SET_TOP(NULL);
+                PyErr_Format(
                     PyExc_TypeError,
-                    "'async for' received an invalid object "
-                    "from __aiter__: %.100s",
+                    "'async for' received an object from __aiter__ "
+                    "that does not implement __anext__: %.100s",
                     Py_TYPE(iter)->tp_name);
-
-                SET_TOP(NULL);
                 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);
     }



More information about the Python-checkins mailing list