Issue #2333: Backport set and dict comprehensions syntax.
diff --git a/Lib/compiler/ast.py b/Lib/compiler/ast.py
index f47434c..4c3fc16 100644
--- a/Lib/compiler/ast.py
+++ b/Lib/compiler/ast.py
@@ -890,6 +890,51 @@
def __repr__(self):
return "ListCompIf(%s)" % (repr(self.test),)
+class SetComp(Node):
+ def __init__(self, expr, quals, lineno=None):
+ self.expr = expr
+ self.quals = quals
+ self.lineno = lineno
+
+ def getChildren(self):
+ children = []
+ children.append(self.expr)
+ children.extend(flatten(self.quals))
+ return tuple(children)
+
+ def getChildNodes(self):
+ nodelist = []
+ nodelist.append(self.expr)
+ nodelist.extend(flatten_nodes(self.quals))
+ return tuple(nodelist)
+
+ def __repr__(self):
+ return "SetComp(%s, %s)" % (repr(self.expr), repr(self.quals))
+
+class DictComp(Node):
+ def __init__(self, key, value, quals, lineno=None):
+ self.key = key
+ self.value = value
+ self.quals = quals
+ self.lineno = lineno
+
+ def getChildren(self):
+ children = []
+ children.append(self.key)
+ children.append(self.value)
+ children.extend(flatten(self.quals))
+ return tuple(children)
+
+ def getChildNodes(self):
+ nodelist = []
+ nodelist.append(self.key)
+ nodelist.append(self.value)
+ nodelist.extend(flatten_nodes(self.quals))
+ return tuple(nodelist)
+
+ def __repr__(self):
+ return "DictComp(%s, %s, %s)" % (repr(self.key), repr(self.value), repr(self.quals))
+
class Mod(Node):
def __init__(self, leftright, lineno=None):
self.left = leftright[0]
diff --git a/Lib/compiler/pyassem.py b/Lib/compiler/pyassem.py
index 88510de..286be0c 100644
--- a/Lib/compiler/pyassem.py
+++ b/Lib/compiler/pyassem.py
@@ -685,7 +685,9 @@
effect = {
'POP_TOP': -1,
'DUP_TOP': 1,
- 'LIST_APPEND': -2,
+ 'LIST_APPEND': -1,
+ 'SET_ADD': -1,
+ 'MAP_ADD': -2,
'SLICE+1': -1,
'SLICE+2': -1,
'SLICE+3': -2,
diff --git a/Lib/compiler/pycodegen.py b/Lib/compiler/pycodegen.py
index bef9c70..4f2ecf2 100644
--- a/Lib/compiler/pycodegen.py
+++ b/Lib/compiler/pycodegen.py
@@ -589,6 +589,55 @@
self.emit('JUMP_ABSOLUTE', start)
self.startBlock(anchor)
+ def visitSetComp(self, node):
+ self.set_lineno(node)
+ # setup list
+ self.emit('BUILD_SET', 0)
+
+ stack = []
+ for i, for_ in zip(range(len(node.quals)), node.quals):
+ start, anchor = self.visit(for_)
+ cont = None
+ for if_ in for_.ifs:
+ if cont is None:
+ cont = self.newBlock()
+ self.visit(if_, cont)
+ stack.insert(0, (start, cont, anchor))
+
+ self.visit(node.expr)
+ self.emit('SET_ADD', len(node.quals) + 1)
+
+ for start, cont, anchor in stack:
+ if cont:
+ self.nextBlock(cont)
+ self.emit('JUMP_ABSOLUTE', start)
+ self.startBlock(anchor)
+
+ def visitDictComp(self, node):
+ self.set_lineno(node)
+ # setup list
+ self.emit('BUILD_MAP', 0)
+
+ stack = []
+ for i, for_ in zip(range(len(node.quals)), node.quals):
+ start, anchor = self.visit(for_)
+ cont = None
+ for if_ in for_.ifs:
+ if cont is None:
+ cont = self.newBlock()
+ self.visit(if_, cont)
+ stack.insert(0, (start, cont, anchor))
+
+ self.visit(node.value)
+ self.visit(node.key)
+ self.emit('MAP_ADD', len(node.quals) + 1)
+
+ for start, cont, anchor in stack:
+ if cont:
+ self.nextBlock(cont)
+ self.emit('JUMP_ABSOLUTE', start)
+ self.startBlock(anchor)
+
def visitListCompFor(self, node):
start = self.newBlock()
anchor = self.newBlock()
diff --git a/Lib/compiler/transformer.py b/Lib/compiler/transformer.py
index 816f13b..d4f4613 100644
--- a/Lib/compiler/transformer.py
+++ b/Lib/compiler/transformer.py
@@ -581,8 +581,10 @@
testlist1 = testlist
exprlist = testlist
- def testlist_gexp(self, nodelist):
- if len(nodelist) == 2 and nodelist[1][0] == symbol.gen_for:
+ def testlist_comp(self, nodelist):
+ # test ( comp_for | (',' test)* [','] )
+ assert nodelist[0][0] == symbol.test
+ if len(nodelist) == 2 and nodelist[1][0] == symbol.comp_for:
test = self.com_node(nodelist[0])
return self.com_generator_expression(test, nodelist[1])
return self.testlist(nodelist)
@@ -1001,7 +1003,7 @@
# loop to avoid trivial recursion
while 1:
t = node[0]
- if t in (symbol.exprlist, symbol.testlist, symbol.testlist_safe, symbol.testlist_gexp):
+ if t in (symbol.exprlist, symbol.testlist, symbol.testlist_safe, symbol.testlist_comp):
if len(node) > 2:
return self.com_assign_tuple(node, assigning)
node = node[1]
@@ -1099,116 +1101,138 @@
else:
stmts.append(result)
- if hasattr(symbol, 'list_for'):
- def com_list_constructor(self, nodelist):
- # listmaker: test ( list_for | (',' test)* [','] )
- values = []
- for i in range(1, len(nodelist)):
- if nodelist[i][0] == symbol.list_for:
- assert len(nodelist[i:]) == 1
- return self.com_list_comprehension(values[0],
- nodelist[i])
- elif nodelist[i][0] == token.COMMA:
- continue
- values.append(self.com_node(nodelist[i]))
- return List(values, lineno=values[0].lineno)
+ def com_list_constructor(self, nodelist):
+ # listmaker: test ( list_for | (',' test)* [','] )
+ values = []
+ for i in range(1, len(nodelist)):
+ if nodelist[i][0] == symbol.list_for:
+ assert len(nodelist[i:]) == 1
+ return self.com_list_comprehension(values[0],
+ nodelist[i])
+ elif nodelist[i][0] == token.COMMA:
+ continue
+ values.append(self.com_node(nodelist[i]))
+ return List(values, lineno=values[0].lineno)
- def com_list_comprehension(self, expr, node):
- # list_iter: list_for | list_if
- # list_for: 'for' exprlist 'in' testlist [list_iter]
- # list_if: 'if' test [list_iter]
+ def com_list_comprehension(self, expr, node):
+ return self.com_comprehension(expr, None, node, 'list')
- # XXX should raise SyntaxError for assignment
+ def com_comprehension(self, expr1, expr2, node, type):
+ # list_iter: list_for | list_if
+ # list_for: 'for' exprlist 'in' testlist [list_iter]
+ # list_if: 'if' test [list_iter]
- lineno = node[1][2]
- fors = []
- while node:
- t = node[1][1]
- if t == 'for':
- assignNode = self.com_assign(node[2], OP_ASSIGN)
- listNode = self.com_node(node[4])
- newfor = ListCompFor(assignNode, listNode, [])
- newfor.lineno = node[1][2]
- fors.append(newfor)
- if len(node) == 5:
- node = None
- else:
- node = self.com_list_iter(node[5])
- elif t == 'if':
- test = self.com_node(node[2])
- newif = ListCompIf(test, lineno=node[1][2])
- newfor.ifs.append(newif)
- if len(node) == 3:
- node = None
- else:
- node = self.com_list_iter(node[3])
+ # XXX should raise SyntaxError for assignment
+ # XXX(avassalotti) Set and dict comprehensions should have generator
+ # semantics. In other words, they shouldn't leak
+ # variables outside of the comprehension's scope.
+
+ lineno = node[1][2]
+ fors = []
+ while node:
+ t = node[1][1]
+ if t == 'for':
+ assignNode = self.com_assign(node[2], OP_ASSIGN)
+ compNode = self.com_node(node[4])
+ newfor = ListCompFor(assignNode, compNode, [])
+ newfor.lineno = node[1][2]
+ fors.append(newfor)
+ if len(node) == 5:
+ node = None
+ elif type == 'list':
+ node = self.com_list_iter(node[5])
else:
- raise SyntaxError, \
- ("unexpected list comprehension element: %s %d"
- % (node, lineno))
- return ListComp(expr, fors, lineno=lineno)
-
- def com_list_iter(self, node):
- assert node[0] == symbol.list_iter
- return node[1]
- else:
- def com_list_constructor(self, nodelist):
- values = []
- for i in range(1, len(nodelist), 2):
- values.append(self.com_node(nodelist[i]))
- return List(values, lineno=values[0].lineno)
-
- if hasattr(symbol, 'gen_for'):
- def com_generator_expression(self, expr, node):
- # gen_iter: gen_for | gen_if
- # gen_for: 'for' exprlist 'in' test [gen_iter]
- # gen_if: 'if' test [gen_iter]
-
- lineno = node[1][2]
- fors = []
- while node:
- t = node[1][1]
- if t == 'for':
- assignNode = self.com_assign(node[2], OP_ASSIGN)
- genNode = self.com_node(node[4])
- newfor = GenExprFor(assignNode, genNode, [],
- lineno=node[1][2])
- fors.append(newfor)
- if (len(node)) == 5:
- node = None
- else:
- node = self.com_gen_iter(node[5])
- elif t == 'if':
- test = self.com_node(node[2])
- newif = GenExprIf(test, lineno=node[1][2])
- newfor.ifs.append(newif)
- if len(node) == 3:
- node = None
- else:
- node = self.com_gen_iter(node[3])
+ node = self.com_comp_iter(node[5])
+ elif t == 'if':
+ test = self.com_node(node[2])
+ newif = ListCompIf(test, lineno=node[1][2])
+ newfor.ifs.append(newif)
+ if len(node) == 3:
+ node = None
+ elif type == 'list':
+ node = self.com_list_iter(node[3])
else:
- raise SyntaxError, \
- ("unexpected generator expression element: %s %d"
- % (node, lineno))
- fors[0].is_outmost = True
- return GenExpr(GenExprInner(expr, fors), lineno=lineno)
+ node = self.com_comp_iter(node[3])
+ else:
+ raise SyntaxError, \
+ ("unexpected comprehension element: %s %d"
+ % (node, lineno))
+ if type == 'list':
+ return ListComp(expr1, fors, lineno=lineno)
+ elif type == 'set':
+ return SetComp(expr1, fors, lineno=lineno)
+ elif type == 'dict':
+ return DictComp(expr1, expr2, fors, lineno=lineno)
+ else:
+ raise ValueError("unexpected comprehension type: " + repr(type))
- def com_gen_iter(self, node):
- assert node[0] == symbol.gen_iter
- return node[1]
+ def com_list_iter(self, node):
+ assert node[0] == symbol.list_iter
+ return node[1]
+
+ def com_comp_iter(self, node):
+ assert node[0] == symbol.comp_iter
+ return node[1]
+
+ def com_generator_expression(self, expr, node):
+ # comp_iter: comp_for | comp_if
+ # comp_for: 'for' exprlist 'in' test [comp_iter]
+ # comp_if: 'if' test [comp_iter]
+
+ lineno = node[1][2]
+ fors = []
+ while node:
+ t = node[1][1]
+ if t == 'for':
+ assignNode = self.com_assign(node[2], OP_ASSIGN)
+ genNode = self.com_node(node[4])
+ newfor = GenExprFor(assignNode, genNode, [],
+ lineno=node[1][2])
+ fors.append(newfor)
+ if (len(node)) == 5:
+ node = None
+ else:
+ node = self.com_comp_iter(node[5])
+ elif t == 'if':
+ test = self.com_node(node[2])
+ newif = GenExprIf(test, lineno=node[1][2])
+ newfor.ifs.append(newif)
+ if len(node) == 3:
+ node = None
+ else:
+ node = self.com_comp_iter(node[3])
+ else:
+ raise SyntaxError, \
+ ("unexpected generator expression element: %s %d"
+ % (node, lineno))
+ fors[0].is_outmost = True
+ return GenExpr(GenExprInner(expr, fors), lineno=lineno)
def com_dictorsetmaker(self, nodelist):
- # dictorsetmaker: ( (test ':' test (',' test ':' test)* [',']) |
- # (test (',' test)* [',']) )
+ # dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) |
+ # (test (comp_for | (',' test)* [','])) )
assert nodelist[0] == symbol.dictorsetmaker
- if len(nodelist) == 2 or nodelist[2][0] == token.COMMA:
+ nodelist = nodelist[1:]
+ if len(nodelist) == 1 or nodelist[1][0] == token.COMMA:
+ # set literal
items = []
- for i in range(1, len(nodelist), 2):
+ for i in range(0, len(nodelist), 2):
items.append(self.com_node(nodelist[i]))
return Set(items, lineno=items[0].lineno)
+ elif nodelist[1][0] == symbol.comp_for:
+ # set comprehension
+ expr = self.com_node(nodelist[0])
+ return self.com_comprehension(expr, None, nodelist[1], 'set')
+ elif len(nodelist) > 3 and nodelist[3][0] == symbol.comp_for:
+ # dict comprehension
+ assert nodelist[1][0] == token.COLON
+ key = self.com_node(nodelist[0])
+ value = self.com_node(nodelist[2])
+ return self.com_comprehension(key, value, nodelist[3], 'dict')
else:
+ # dict literal
items = []
- for i in range(1, len(nodelist), 4):
+ for i in range(0, len(nodelist), 4):
items.append((self.com_node(nodelist[i]),
self.com_node(nodelist[i+2])))
return Dict(items, lineno=items[0][0].lineno)
@@ -1257,7 +1281,7 @@
kw, result = self.com_argument(node, kw, star_node)
if len_nodelist != 2 and isinstance(result, GenExpr) \
- and len(node) == 3 and node[2][0] == symbol.gen_for:
+ and len(node) == 3 and node[2][0] == symbol.comp_for:
# allow f(x for x in y), but reject f(x for x in y, 1)
# should use f((x for x in y), 1) instead of f(x for x in y, 1)
raise SyntaxError, 'generator expression needs parenthesis'
@@ -1269,7 +1293,7 @@
lineno=extractLineNo(nodelist))
def com_argument(self, nodelist, kw, star_node):
- if len(nodelist) == 3 and nodelist[2][0] == symbol.gen_for:
+ if len(nodelist) == 3 and nodelist[2][0] == symbol.comp_for:
test = self.com_node(nodelist[1])
return 0, self.com_generator_expression(test, nodelist[2])
if len(nodelist) == 2:
diff --git a/Lib/opcode.py b/Lib/opcode.py
index f11e475..e403365 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -186,5 +186,7 @@
def_op('EXTENDED_ARG', 145)
EXTENDED_ARG = 145
+def_op('SET_ADD', 146)
+def_op('MAP_ADD', 147)
del def_op, name_op, jrel_op, jabs_op
diff --git a/Lib/symbol.py b/Lib/symbol.py
index c0bb145..fc3c34a 100755
--- a/Lib/symbol.py
+++ b/Lib/symbol.py
@@ -74,7 +74,7 @@
power = 317
atom = 318
listmaker = 319
-testlist_gexp = 320
+testlist_comp = 320
lambdef = 321
trailer = 322
subscriptlist = 323
@@ -90,9 +90,9 @@
list_iter = 333
list_for = 334
list_if = 335
-gen_iter = 336
-gen_for = 337
-gen_if = 338
+comp_iter = 336
+comp_for = 337
+comp_if = 338
testlist1 = 339
encoding_decl = 340
yield_expr = 341
diff --git a/Lib/test/test_compiler.py b/Lib/test/test_compiler.py
index 8d4a3a99..b54894d 100644
--- a/Lib/test/test_compiler.py
+++ b/Lib/test/test_compiler.py
@@ -140,6 +140,36 @@
'eval')
self.assertEquals(eval(c), [(0, 3), (1, 3), (2, 3)])
+ def testSetLiteral(self):
+ c = compiler.compile('{1, 2, 3}', '<string>', 'eval')
+ self.assertEquals(eval(c), {1,2,3})
+ c = compiler.compile('{1, 2, 3,}', '<string>', 'eval')
+ self.assertEquals(eval(c), {1,2,3})
+
+ def testDictLiteral(self):
+ c = compiler.compile('{1:2, 2:3, 3:4}', '<string>', 'eval')
+ self.assertEquals(eval(c), {1:2, 2:3, 3:4})
+ c = compiler.compile('{1:2, 2:3, 3:4,}', '<string>', 'eval')
+ self.assertEquals(eval(c), {1:2, 2:3, 3:4})
+
+ def testSetComp(self):
+ c = compiler.compile('{x for x in range(1, 4)}', '<string>', 'eval')
+ self.assertEquals(eval(c), {1, 2, 3})
+ c = compiler.compile('{x * y for x in range(3) if x != 0'
+ ' for y in range(4) if y != 0}',
+ '<string>',
+ 'eval')
+ self.assertEquals(eval(c), {1, 2, 3, 4, 6})
+
+ def testDictComp(self):
+ c = compiler.compile('{x:x+1 for x in range(1, 4)}', '<string>', 'eval')
+ self.assertEquals(eval(c), {1:2, 2:3, 3:4})
+ c = compiler.compile('{(x, y) : y for x in range(2) if x != 0'
+ ' for y in range(3) if y != 0}',
+ '<string>',
+ 'eval')
+ self.assertEquals(eval(c), {(1, 2): 2, (1, 1): 1})
+
def testWith(self):
# SF bug 1638243
c = compiler.compile('from __future__ import with_statement\n'
@@ -248,6 +278,8 @@
l[3:4]
d = {'a': 2}
d = {}
+d = {x: y for x, y in zip(range(5), range(5,10))}
+s = {x for x in range(10)}
s = {1}
t = ()
t = (1, 2)
diff --git a/Lib/test/test_dictcomps.py b/Lib/test/test_dictcomps.py
new file mode 100644
index 0000000..9af9e48
--- /dev/null
+++ b/Lib/test/test_dictcomps.py
@@ -0,0 +1,54 @@
+
+doctests = """
+
+ >>> k = "old value"
+ >>> { k: None for k in range(10) }
+ {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, 6: None, 7: None, 8: None, 9: None}
+ >>> k
+ 'old value'
+
+ >>> { k: k+10 for k in range(10) }
+ {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18, 9: 19}
+
+ >>> g = "Global variable"
+ >>> { k: g for k in range(10) }
+ {0: 'Global variable', 1: 'Global variable', 2: 'Global variable', 3: 'Global variable', 4: 'Global variable', 5: 'Global variable', 6: 'Global variable', 7: 'Global variable', 8: 'Global variable', 9: 'Global variable'}
+
+ >>> { k: v for k in range(10) for v in range(10) if k == v }
+ {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
+
+ >>> { k: v for v in range(10) for k in range(v*9, v*10) }
+ {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, 85: 9, 86: 9, 87: 9, 88: 9, 89: 9}
+
+ >>> { x: y for y, x in ((1, 2), (3, 4)) } = 5 # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ SyntaxError: ...
+
+ >>> { x: y for y, x in ((1, 2), (3, 4)) } += 5 # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ SyntaxError: ...
+
+"""
+
+__test__ = {'doctests' : doctests}
+
+def test_main(verbose=None):
+ import sys
+ from test import test_support
+ from test import test_dictcomps
+ test_support.run_doctest(test_dictcomps, verbose)
+
+ # verify reference counting
+ if verbose and hasattr(sys, "gettotalrefcount"):
+ import gc
+ counts = [None] * 5
+ for i in range(len(counts)):
+ test_support.run_doctest(test_dictcomps, verbose)
+ gc.collect()
+ counts[i] = sys.gettotalrefcount()
+ print(counts)
+
+if __name__ == "__main__":
+ test_main(verbose=True)
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index 4713d1a..12039e7 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -808,6 +808,13 @@
pass
self.assertEqual(G.decorated, True)
+ def testDictcomps(self):
+ # dictorsetmaker: ( (test ':' test (comp_for |
+ # (',' test ':' test)* [','])) |
+ # (test (comp_for | (',' test)* [','])) )
+ nums = [1, 2, 3]
+ self.assertEqual({i:i+1 for i in nums}, {1: 2, 2: 3, 3: 4})
+
def testListcomps(self):
# list comprehension tests
nums = [1, 2, 3, 4, 5]
diff --git a/Lib/test/test_parser.py b/Lib/test/test_parser.py
index 03803d9..6ae9975 100644
--- a/Lib/test/test_parser.py
+++ b/Lib/test/test_parser.py
@@ -76,9 +76,20 @@
self.check_expr("[x**3 for x in range(20)]")
self.check_expr("[x**3 for x in range(20) if x % 3]")
self.check_expr("[x**3 for x in range(20) if x % 2 if x % 3]")
+ self.check_expr("[x+y for x in range(30) for y in range(20) if x % 2 if y % 3]")
+ #self.check_expr("[x for x in lambda: True, lambda: False if x()]")
self.check_expr("list(x**3 for x in range(20))")
self.check_expr("list(x**3 for x in range(20) if x % 3)")
self.check_expr("list(x**3 for x in range(20) if x % 2 if x % 3)")
+ self.check_expr("list(x+y for x in range(30) for y in range(20) if x % 2 if y % 3)")
+ self.check_expr("{x**3 for x in range(30)}")
+ self.check_expr("{x**3 for x in range(30) if x % 3}")
+ self.check_expr("{x**3 for x in range(30) if x % 2 if x % 3}")
+ self.check_expr("{x+y for x in range(30) for y in range(20) if x % 2 if y % 3}")
+ self.check_expr("{x**3: y**2 for x, y in zip(range(30), range(30))}")
+ self.check_expr("{x**3: y**2 for x, y in zip(range(30), range(30)) if x % 3}")
+ self.check_expr("{x**3: y**2 for x, y in zip(range(30), range(30)) if x % 3 if y % 3}")
+ self.check_expr("{x:y for x in range(30) for y in range(20) if x % 2 if y % 3}")
self.check_expr("foo(*args)")
self.check_expr("foo(*args, **kw)")
self.check_expr("foo(**kw)")
@@ -107,6 +118,7 @@
self.check_expr("lambda foo=bar, blaz=blat+2, **z: 0")
self.check_expr("lambda foo=bar, blaz=blat+2, *y, **z: 0")
self.check_expr("lambda x, *y, **z: 0")
+ self.check_expr("lambda x: 5 if x else 2")
self.check_expr("(x for x in range(10))")
self.check_expr("foo(x for x in range(10))")
diff --git a/Lib/test/test_setcomps.py b/Lib/test/test_setcomps.py
new file mode 100644
index 0000000..db5e6f7
--- /dev/null
+++ b/Lib/test/test_setcomps.py
@@ -0,0 +1,151 @@
+doctests = """
+########### Tests mostly copied from test_listcomps.py ############
+
+Test simple loop with conditional
+
+ >>> sum({i*i for i in range(100) if i&1 == 1})
+ 166650
+
+Test simple case
+
+ >>> {2*y + x + 1 for x in (0,) for y in (1,)}
+ set([3])
+
+Test simple nesting
+
+ >>> list(sorted({(i,j) for i in range(3) for j in range(4)}))
+ [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)]
+
+Test nesting with the inner expression dependent on the outer
+
+ >>> list(sorted({(i,j) for i in range(4) for j in range(i)}))
+ [(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2)]
+
+Make sure the induction variable is not exposed
+
+ >>> i = 20
+ >>> sum({i*i for i in range(100)})
+ 328350
+
+ >>> i
+ 20
+
+Verify that syntax error's are raised for setcomps used as lvalues
+
+ >>> {y for y in (1,2)} = 10 # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ SyntaxError: ...
+
+ >>> {y for y in (1,2)} += 10 # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ SyntaxError: ...
+
+
+Make a nested set comprehension that acts like set(range())
+
+ >>> def srange(n):
+ ... return {i for i in range(n)}
+ >>> list(sorted(srange(10)))
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+Same again, only as a lambda expression instead of a function definition
+
+ >>> lrange = lambda n: {i for i in range(n)}
+ >>> list(sorted(lrange(10)))
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+Generators can call other generators:
+
+ >>> def grange(n):
+ ... for x in {i for i in range(n)}:
+ ... yield x
+ >>> list(sorted(grange(5)))
+ [0, 1, 2, 3, 4]
+
+
+Make sure that None is a valid return value
+
+ >>> {None for i in range(10)}
+ set([None])
+
+########### Tests for various scoping corner cases ############
+
+Return lambdas that use the iteration variable as a default argument
+
+ >>> items = {(lambda i=i: i) for i in range(5)}
+ >>> {x() for x in items} == set(range(5))
+ True
+
+Same again, only this time as a closure variable
+
+ >>> items = {(lambda: i) for i in range(5)}
+ >>> {x() for x in items}
+ set([4])
+
+Another way to test that the iteration variable is local to the list comp
+
+ >>> items = {(lambda: i) for i in range(5)}
+ >>> i = 20
+ >>> {x() for x in items}
+ set([4])
+
+And confirm that a closure can jump over the list comp scope
+
+ >>> items = {(lambda: y) for i in range(5)}
+ >>> y = 2
+ >>> {x() for x in items}
+ set([2])
+
+We also repeat each of the above scoping tests inside a function
+
+ >>> def test_func():
+ ... items = {(lambda i=i: i) for i in range(5)}
+ ... return {x() for x in items}
+ >>> test_func() == set(range(5))
+ True
+
+ >>> def test_func():
+ ... items = {(lambda: i) for i in range(5)}
+ ... return {x() for x in items}
+ >>> test_func()
+ set([4])
+
+ >>> def test_func():
+ ... items = {(lambda: i) for i in range(5)}
+ ... i = 20
+ ... return {x() for x in items}
+ >>> test_func()
+ set([4])
+
+ >>> def test_func():
+ ... items = {(lambda: y) for i in range(5)}
+ ... y = 2
+ ... return {x() for x in items}
+ >>> test_func()
+ set([2])
+
+"""
+
+
+__test__ = {'doctests' : doctests}
+
+def test_main(verbose=None):
+ import sys
+ from test import test_support
+ from test import test_setcomps
+ test_support.run_doctest(test_setcomps, verbose)
+
+ # verify reference counting
+ if verbose and hasattr(sys, "gettotalrefcount"):
+ import gc
+ counts = [None] * 5
+ for i in range(len(counts)):
+ test_support.run_doctest(test_setcomps, verbose)
+ gc.collect()
+ counts[i] = sys.gettotalrefcount()
+ print(counts)
+
+if __name__ == "__main__":
+ test_main(verbose=True)