]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-127682: Only call `__iter__` once in generator expressions. (GH-132351)
authorMark Shannon <mark@hotpy.org>
Fri, 11 Apr 2025 08:37:22 +0000 (09:37 +0100)
committerGitHub <noreply@github.com>
Fri, 11 Apr 2025 08:37:22 +0000 (09:37 +0100)
Lib/test/test_dis.py
Lib/test/test_generators.py
Lib/test/test_genexps.py
Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst [new file with mode: 0644]
Python/codegen.c

index 58ba86fb43092ad21492a5523d196e0d870c09b4..b53a365642930031fbe346131e24e48c022e7345 100644 (file)
@@ -204,7 +204,6 @@ dis_bug1333982 = """\
               LOAD_CONST               1 (<code object <genexpr> at 0x..., file "%s", line %d>)
               MAKE_FUNCTION
               LOAD_FAST_BORROW         0 (x)
-              GET_ITER
               CALL                     0
 
 %3d           LOAD_SMALL_INT           1
@@ -821,7 +820,6 @@ Disassembly of <code object foo at 0x..., file "%s", line %d>:
                MAKE_FUNCTION
                SET_FUNCTION_ATTRIBUTE   8 (closure)
                LOAD_DEREF               1 (y)
-               GET_ITER
                CALL                     0
                CALL                     1
                RETURN_VALUE
index 8bce42f037478cf57ad2e2b1c9124b8eb9db6e90..3e41c7b9663491a05508d0b0e1f3a968b8b510d0 100644 (file)
@@ -268,6 +268,28 @@ class GeneratorTest(unittest.TestCase):
         #This should not raise
         loop()
 
+    def test_genexpr_only_calls_dunder_iter_once(self):
+
+        class Iterator:
+
+            def __init__(self):
+                self.val = 0
+
+            def __next__(self):
+                if self.val == 2:
+                    raise StopIteration
+                self.val += 1
+                return self.val
+
+            # No __iter__ method
+
+        class C:
+
+            def __iter__(self):
+                return Iterator()
+
+        self.assertEqual([1,2], list(i for i in C()))
+
 
 class ModifyUnderlyingIterableTest(unittest.TestCase):
     iterables = [
index 7fb58a67368576a56856645c23a8137ca554b6ed..fe5f18fa3f88a016b65a6711a45c2439b1490c61 100644 (file)
@@ -123,15 +123,6 @@ Verify early binding for the outermost for-expression
     >>> list(g)
     [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 
-Verify that the outermost for-expression makes an immediate check
-for iterability
-
-    >>> (i for i in 6)
-    Traceback (most recent call last):
-      File "<pyshell#4>", line 1, in -toplevel-
-        (i for i in 6)
-    TypeError: 'int' object is not iterable
-
 Verify late binding for the outermost if-expression
 
     >>> include = (2,4,6,8)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst
new file mode 100644 (file)
index 0000000..b87750e
--- /dev/null
@@ -0,0 +1,4 @@
+No longer call ``__iter__`` twice when creating and executing a generator expression.
+Creating a generator expression from a non-interable will raise only when the
+generator expression is executed.
+This brings the behavior of generator expressions in line with other generators.
index 379d37c65ca8e6695c196bcab62f83153e58041a..35b46dcdc4095086ac5ed5246fb1f43dc9d9cb15 100644 (file)
@@ -4775,10 +4775,7 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
     }
     Py_CLEAR(co);
 
-    if (codegen_comprehension_iter(c, outermost)) {
-        goto error;
-    }
-
+    VISIT(c, expr, outermost->iter);
     ADDOP_I(c, loc, CALL, 0);
 
     if (is_async_comprehension && type != COMP_GENEXP) {