[Python-checkins] cpython: make delegating generators say they running (closes #14220)

benjamin.peterson python-checkins at python.org
Thu Mar 8 00:57:15 CET 2012


http://hg.python.org/cpython/rev/3357eac1ba62
changeset:   75479:3357eac1ba62
user:        Benjamin Peterson <benjamin at python.org>
date:        Wed Mar 07 17:57:04 2012 -0600
summary:
  make delegating generators say they running (closes #14220)

files:
  Lib/test/test_pep380.py |  72 +++++++++++++++++++++++++++++
  Misc/NEWS               |   3 +
  Objects/genobject.c     |  51 ++++++++++++++-----
  3 files changed, 112 insertions(+), 14 deletions(-)


diff --git a/Lib/test/test_pep380.py b/Lib/test/test_pep380.py
--- a/Lib/test/test_pep380.py
+++ b/Lib/test/test_pep380.py
@@ -847,6 +847,78 @@
             yield from ()
         self.assertRaises(StopIteration, next, g())
 
+    def test_delegating_generators_claim_to_be_running(self):
+        # Check with basic iteration
+        def one():
+            yield 0
+            yield from two()
+            yield 3
+        def two():
+            yield 1
+            try:
+                yield from g1
+            except ValueError:
+                pass
+            yield 2
+        g1 = one()
+        self.assertEqual(list(g1), [0, 1, 2, 3])
+        # Check with send
+        g1 = one()
+        res = [next(g1)]
+        try:
+            while True:
+                res.append(g1.send(42))
+        except StopIteration:
+            pass
+        self.assertEqual(res, [0, 1, 2, 3])
+        # Check with throw
+        class MyErr(Exception):
+            pass
+        def one():
+            try:
+                yield 0
+            except MyErr:
+                pass
+            yield from two()
+            try:
+                yield 3
+            except MyErr:
+                pass
+        def two():
+            try:
+                yield 1
+            except MyErr:
+                pass
+            try:
+                yield from g1
+            except ValueError:
+                pass
+            try:
+                yield 2
+            except MyErr:
+                pass
+        g1 = one()
+        res = [next(g1)]
+        try:
+            while True:
+                res.append(g1.throw(MyErr))
+        except StopIteration:
+            pass
+        # Check with close
+        class MyIt(object):
+            def __iter__(self):
+                return self
+            def __next__(self):
+                return 42
+            def close(self_):
+                self.assertTrue(g1.gi_running)
+                self.assertRaises(ValueError, next, g1)
+        def one():
+            yield from MyIt()
+        g1 = one()
+        next(g1)
+        g1.close()
+
 
 def test_main():
     from test import support
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,9 @@
 - Issue #14205: dict lookup raises a RuntimeError if the dict is modified
   during a lookup.
 
+- Issue #14220: When a generator is delegating to another iterator with the
+  yield from syntax, it needs to have its ``gi_running`` flag set to True.
+
 Library
 -------
 
diff --git a/Objects/genobject.c b/Objects/genobject.c
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -41,6 +41,15 @@
     PyObject_GC_Del(gen);
 }
 
+static int
+gen_running(PyGenObject *gen)
+{
+    if (gen->gi_running) {
+        PyErr_SetString(PyExc_ValueError, "generator already executing");
+        return 1;
+    }
+    return 0;
+}
 
 static PyObject *
 gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
@@ -49,11 +58,7 @@
     PyFrameObject *f = gen->gi_frame;
     PyObject *result;
 
-    if (gen->gi_running) {
-        PyErr_SetString(PyExc_ValueError,
-                        "generator already executing");
-        return NULL;
-    }
+    assert(!gen->gi_running);
     if (f==NULL || f->f_stacktop == NULL) {
         /* Only set exception if called from send() */
         if (arg && !exc)
@@ -137,12 +142,15 @@
     int exc = 0;
     PyObject *ret;
     PyObject *yf = gen->gi_frame ? gen->gi_frame->f_yieldfrom : NULL;
+    if (gen_running(gen))
+        return NULL;
     /* XXX (ncoghlan): Are the incref/decref on arg and yf strictly needed?
      *                 Or would it be valid to rely on borrowed references?
      */
     Py_INCREF(arg);
     if (yf) {
         Py_INCREF(yf);
+        gen->gi_running = 1;
         if (PyGen_CheckExact(yf)) {
             ret = gen_send((PyGenObject *)yf, arg);
         } else {
@@ -151,6 +159,7 @@
             else
                 ret = PyObject_CallMethod(yf, "send", "O", arg);
         }
+        gen->gi_running = 0;
         if (ret) {
             Py_DECREF(yf);
             goto done;
@@ -177,17 +186,19 @@
  */
 
 static int
-gen_close_iter(PyObject *yf)
+gen_close_iter(PyGenObject *gen, PyObject *yf)
 {
     PyObject *retval = NULL;
+    int err = 0;
     
     if (PyGen_CheckExact(yf)) {
         retval = gen_close((PyGenObject *)yf, NULL);
-        if (retval == NULL) {
-            return -1;
-        }
+        if (!retval)
+            err = -1;
     } else {
-        PyObject *meth = PyObject_GetAttrString(yf, "close");
+        PyObject *meth;
+        gen->gi_running = 1;
+        meth = PyObject_GetAttrString(yf, "close");
         if (meth == NULL) {
             if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
                 PyErr_WriteUnraisable(yf);
@@ -197,11 +208,12 @@
             retval = PyObject_CallFunction(meth, "");
             Py_DECREF(meth);
             if (!retval)
-                return -1;
+                err = -1;
         }
+        gen->gi_running = 0;
     }
     Py_XDECREF(retval);
-    return 0;
+    return err;
 }       
 
 static PyObject *
@@ -211,9 +223,11 @@
     PyObject *yf = gen->gi_frame ? gen->gi_frame->f_yieldfrom : NULL;
     int err = 0;
 
+    if (gen_running(gen))
+        return NULL;
     if (yf) {
         Py_INCREF(yf);
-        err = gen_close_iter(yf);
+        err = gen_close_iter(gen, yf);
         gen_undelegate(gen);
         Py_DECREF(yf);
     }
@@ -314,18 +328,22 @@
     if (!PyArg_UnpackTuple(args, "throw", 1, 3, &typ, &val, &tb))
         return NULL;
 
+    if (gen_running(gen))
+        return NULL;
+
     if (yf) {
         PyObject *ret;
         int err;
         Py_INCREF(yf);
         if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit)) {
-            err = gen_close_iter(yf);
+            err = gen_close_iter(gen, yf);
             Py_DECREF(yf);
             gen_undelegate(gen);
             if (err < 0)
                 return gen_send_ex(gen, Py_None, 1);
             goto throw_here;
         }
+        gen->gi_running = 1;
         if (PyGen_CheckExact(yf)) {
             ret = gen_throw((PyGenObject *)yf, args);
         } else {
@@ -343,6 +361,7 @@
             ret = PyObject_CallObject(meth, args);
             Py_DECREF(meth);
         }
+        gen->gi_running = 0;
         Py_DECREF(yf);
         if (!ret) {
             PyObject *val;
@@ -423,10 +442,14 @@
     PyObject *ret;
     int exc = 0;
     PyObject *yf = gen->gi_frame ? gen->gi_frame->f_yieldfrom : NULL;
+    if (gen_running(gen))
+        return NULL;
     if (yf) {
         Py_INCREF(yf);
         /* ceval.c ensures that yf is an iterator */
+        gen->gi_running = 1;
         ret = Py_TYPE(yf)->tp_iternext(yf);
+        gen->gi_running = 0;
         if (ret) {
             Py_DECREF(yf);
             return ret;

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list