summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/reference/datamodel.rst53
-rw-r--r--Include/genobject.h1
-rw-r--r--Lib/asyncio/streams.py26
-rw-r--r--Lib/test/test_collections.py4
-rw-r--r--Lib/test/test_coroutines.py110
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2017-10-06-00-27-04.bpo-31709._PmU51.rst1
-rw-r--r--Objects/genobject.c94
-rw-r--r--Python/ceval.c52
-rw-r--r--Python/compile.c6
9 files changed, 47 insertions, 300 deletions
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index 31a7671..153b58b 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 8c1825f..b9db9f9 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 a82cc79..9fda853 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
-
- @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 75defa1..7e106af 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 ebd880b..0803517 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 0000000..6c342ea
--- /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 1a8c37a..5d5798c 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 cf0c6c9..86ffec4 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 df5070a..431e753 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);
}