do not let set/dict comprehensions leak in Py2, only list comprehensions
authorStefan Behnel <scoder@users.berlios.de>
Tue, 6 Jul 2010 05:20:22 +0000 (07:20 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Tue, 6 Jul 2010 05:20:22 +0000 (07:20 +0200)
Cython/Compiler/ExprNodes.py
Cython/Compiler/Parsing.py
tests/run/cython3.pyx
tests/run/dictcomp.pyx
tests/run/setcomp.pyx

index f5fc042581517959b7bd29da2e372c0a76dc3ad9..2f33e9cf6d3212c018dfda940b43b7729eb98416 100755 (executable)
@@ -3940,8 +3940,10 @@ class ComprehensionNode(ScopedExprNode):
     subexprs = ["target"]
     child_attrs = ["loop", "append"]
 
-    # different behaviour in Py2 and Py3: leak loop variables or not?
-    has_local_scope = False # Py2 behaviour as default
+    # leak loop variables or not?  non-leaking Py3 behaviour is
+    # default, except for list comprehensions where the behaviour
+    # differs in Py2 and Py3 (see Parsing.py)
+    has_local_scope = True
 
     def infer_type(self, env):
         return self.target.infer_type(env)
index 22204db094026b62827838330394221c57b5013e..1cd0a67e8d51e32993e39b23bba83a086905abf1 100644 (file)
@@ -780,7 +780,9 @@ def p_list_maker(s):
         loop = p_comp_for(s, append)
         s.expect(']')
         return ExprNodes.ComprehensionNode(
-            pos, loop=loop, append=append, target=target)
+            pos, loop=loop, append=append, target=target,
+            # list comprehensions leak their loop variable in Py2
+            has_local_scope = s.context.language_level > 2)
     else:
         if s.sy == ',':
             s.next()
index 37442e92960a2a7195af6a321c5163c24ea4927e..8c79cec6ad7f6febbfde921156c8924ac4fb8843 100644 (file)
@@ -1,5 +1,13 @@
 # cython: language_level=3
 
+try:
+    sorted
+except NameError:
+    def sorted(seq):
+        seq = list(seq)
+        seq.sort()
+        return seq
+
 def print_function(*args):
     """
     >>> print_function(1,2,3)
@@ -17,3 +25,33 @@ def unicode_literals():
     """
     print(isinstance(ustring, unicode) or type(ustring))
     return ustring
+
+def list_comp():
+    """
+    >>> list_comp()
+    [0, 4, 8]
+    """
+    x = 'abc'
+    result = [x*2 for x in range(5) if x % 2 == 0]
+    assert x == 'abc' # don't leak in Py3 code
+    return result
+
+def set_comp():
+    """
+    >>> sorted(set_comp())
+    [0, 4, 8]
+    """
+    x = 'abc'
+    result = {x*2 for x in range(5) if x % 2 == 0}
+    assert x == 'abc' # don't leak
+    return result
+
+def dict_comp():
+    """
+    >>> sorted(dict_comp().items())
+    [(0, 0), (2, 4), (4, 8)]
+    """
+    x = 'abc'
+    result = {x:x*2 for x in range(5) if x % 2 == 0}
+    assert x == 'abc' # don't leak
+    return result
index d53310f31ea860885727cb0bbeeb0b516bd67947..08a6dca73d1ad15bfd77141e694885e564756bc0 100644 (file)
@@ -12,7 +12,7 @@ def dictcomp():
     result = { x+2:x*2
                for x in range(5)
                if x % 2 == 0 }
-    assert x != 'abc'
+    assert x == 'abc' # do not leak!
     return result
 
 @cython.test_fail_if_path_exists(
index a89ca5154ff45e4e1d85d80c5a44544f181d6a43..ea6a290196d674f5292d7886a93d80060ddc039e 100644 (file)
@@ -13,9 +13,12 @@ def setcomp():
     >>> sorted(setcomp())
     [0, 4, 8]
     """
-    return { x*2
+    x = 'abc'
+    result = { x*2
              for x in range(5)
              if x % 2 == 0 }
+    assert x == 'abc' # do not leak
+    return result
 
 @cython.test_fail_if_path_exists(
     "//GeneratorExpressionNode",
@@ -30,9 +33,12 @@ def genexp_set():
     >>> sorted(genexp_set())
     [0, 4, 8]
     """
-    return set( x*2
-                 for x in range(5)
-                 if x % 2 == 0 )
+    x = 'abc'
+    result = set( x*2
+                  for x in range(5)
+                  if x % 2 == 0 )
+    assert x == 'abc' # do not leak
+    return result
 
 cdef class A:
     def __repr__(self): return u"A"