diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/compiler/ast.py | 45 | ||||
-rw-r--r-- | Lib/compiler/pyassem.py | 4 | ||||
-rw-r--r-- | Lib/compiler/pycodegen.py | 49 | ||||
-rw-r--r-- | Lib/compiler/transformer.py | 232 | ||||
-rw-r--r-- | Lib/opcode.py | 2 | ||||
-rwxr-xr-x | Lib/symbol.py | 8 | ||||
-rw-r--r-- | Lib/test/test_compiler.py | 32 | ||||
-rw-r--r-- | Lib/test/test_dictcomps.py | 54 | ||||
-rw-r--r-- | Lib/test/test_grammar.py | 7 | ||||
-rw-r--r-- | Lib/test/test_parser.py | 12 | ||||
-rw-r--r-- | Lib/test/test_setcomps.py | 151 |
11 files changed, 487 insertions, 109 deletions
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 @@ class ListCompIf(Node): 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 @@ class StackDepthTracker: 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 @@ class CodeGenerator: 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 @@ class Transformer: 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 @@ class Transformer: # 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 @@ class Transformer: 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_comprehension(self, expr, node): - # list_iter: list_for | list_if - # list_for: 'for' exprlist 'in' testlist [list_iter] - # list_if: 'if' test [list_iter] - - # XXX should raise SyntaxError for assignment - - 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]) + 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): + return self.com_comprehension(expr, None, node, 'list') + + 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] + + # 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_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_gen_iter(self, node): - assert node[0] == symbol.gen_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 @@ class Transformer: 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 @@ class Transformer: 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 @@ jrel_op('SETUP_WITH', 143) 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 @@ factor = 316 power = 317 atom = 318 listmaker = 319 -testlist_gexp = 320 +testlist_comp = 320 lambdef = 321 trailer = 322 subscriptlist = 323 @@ -90,9 +90,9 @@ argument = 332 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 @@ class CompilerTest(unittest.TestCase): '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[0] 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 @@ hello world 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 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase): 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 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase): 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) |