1doctests = """ 2 3Test simple loop with conditional 4 5 >>> sum(i*i for i in range(100) if i&1 == 1) 6 166650 7 8Test simple nesting 9 10 >>> list((i,j) for i in range(3) for j in range(4) ) 11 [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)] 12 13Test nesting with the inner expression dependent on the outer 14 15 >>> list((i,j) for i in range(4) for j in range(i) ) 16 [(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2)] 17 18Test the idiom for temporary variable assignment in comprehensions. 19 20 >>> list((j*j for i in range(4) for j in [i+1])) 21 [1, 4, 9, 16] 22 >>> list((j*k for i in range(4) for j in [i+1] for k in [j+1])) 23 [2, 6, 12, 20] 24 >>> list((j*k for i in range(4) for j, k in [(i+1, i+2)])) 25 [2, 6, 12, 20] 26 27Not assignment 28 29 >>> list((i*i for i in [*range(4)])) 30 [0, 1, 4, 9] 31 >>> list((i*i for i in (*range(4),))) 32 [0, 1, 4, 9] 33 34Make sure the induction variable is not exposed 35 36 >>> i = 20 37 >>> sum(i*i for i in range(100)) 38 328350 39 >>> i 40 20 41 42Test first class 43 44 >>> g = (i*i for i in range(4)) 45 >>> type(g) 46 <class 'generator'> 47 >>> list(g) 48 [0, 1, 4, 9] 49 50Test direct calls to next() 51 52 >>> g = (i*i for i in range(3)) 53 >>> next(g) 54 0 55 >>> next(g) 56 1 57 >>> next(g) 58 4 59 >>> next(g) 60 Traceback (most recent call last): 61 File "<pyshell#21>", line 1, in -toplevel- 62 next(g) 63 StopIteration 64 65Does it stay stopped? 66 67 >>> next(g) 68 Traceback (most recent call last): 69 File "<pyshell#21>", line 1, in -toplevel- 70 next(g) 71 StopIteration 72 >>> list(g) 73 [] 74 75Test running gen when defining function is out of scope 76 77 >>> def f(n): 78 ... return (i*i for i in range(n)) 79 >>> list(f(10)) 80 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 81 82 >>> def f(n): 83 ... return ((i,j) for i in range(3) for j in range(n)) 84 >>> list(f(4)) 85 [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)] 86 >>> def f(n): 87 ... return ((i,j) for i in range(3) for j in range(4) if j in range(n)) 88 >>> list(f(4)) 89 [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)] 90 >>> list(f(2)) 91 [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)] 92 93Verify that parenthesis are required in a statement 94 95 >>> def f(n): 96 ... return i*i for i in range(n) 97 Traceback (most recent call last): 98 ... 99 SyntaxError: invalid syntax 100 101Verify that parenthesis are required when used as a keyword argument value 102 103 >>> dict(a = i for i in range(10)) 104 Traceback (most recent call last): 105 ... 106 SyntaxError: invalid syntax 107 108Verify that parenthesis are required when used as a keyword argument value 109 110 >>> dict(a = (i for i in range(10))) #doctest: +ELLIPSIS 111 {'a': <generator object <genexpr> at ...>} 112 113Verify early binding for the outermost for-expression 114 115 >>> x=10 116 >>> g = (i*i for i in range(x)) 117 >>> x = 5 118 >>> list(g) 119 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 120 121Verify that the outermost for-expression makes an immediate check 122for iterability 123 124 >>> (i for i in 6) 125 Traceback (most recent call last): 126 File "<pyshell#4>", line 1, in -toplevel- 127 (i for i in 6) 128 TypeError: 'int' object is not iterable 129 130Verify late binding for the outermost if-expression 131 132 >>> include = (2,4,6,8) 133 >>> g = (i*i for i in range(10) if i in include) 134 >>> include = (1,3,5,7,9) 135 >>> list(g) 136 [1, 9, 25, 49, 81] 137 138Verify late binding for the innermost for-expression 139 140 >>> g = ((i,j) for i in range(3) for j in range(x)) 141 >>> x = 4 142 >>> list(g) 143 [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)] 144 145Verify re-use of tuples (a side benefit of using genexps over listcomps) 146 147 >>> tupleids = list(map(id, ((i,i) for i in range(10)))) 148 >>> int(max(tupleids) - min(tupleids)) 149 0 150 151Verify that syntax error's are raised for genexps used as lvalues 152 153 >>> (y for y in (1,2)) = 10 154 Traceback (most recent call last): 155 ... 156 SyntaxError: cannot assign to generator expression 157 158 >>> (y for y in (1,2)) += 10 159 Traceback (most recent call last): 160 ... 161 SyntaxError: 'generator expression' is an illegal expression for augmented assignment 162 163 164########### Tests borrowed from or inspired by test_generators.py ############ 165 166Make a generator that acts like range() 167 168 >>> yrange = lambda n: (i for i in range(n)) 169 >>> list(yrange(10)) 170 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 171 172Generators always return to the most recent caller: 173 174 >>> def creator(): 175 ... r = yrange(5) 176 ... print("creator", next(r)) 177 ... return r 178 >>> def caller(): 179 ... r = creator() 180 ... for i in r: 181 ... print("caller", i) 182 >>> caller() 183 creator 0 184 caller 1 185 caller 2 186 caller 3 187 caller 4 188 189Generators can call other generators: 190 191 >>> def zrange(n): 192 ... for i in yrange(n): 193 ... yield i 194 >>> list(zrange(5)) 195 [0, 1, 2, 3, 4] 196 197 198Verify that a gen exp cannot be resumed while it is actively running: 199 200 >>> g = (next(me) for i in range(10)) 201 >>> me = g 202 >>> next(me) 203 Traceback (most recent call last): 204 File "<pyshell#30>", line 1, in -toplevel- 205 next(me) 206 File "<pyshell#28>", line 1, in <generator expression> 207 g = (next(me) for i in range(10)) 208 ValueError: generator already executing 209 210Verify exception propagation 211 212 >>> g = (10 // i for i in (5, 0, 2)) 213 >>> next(g) 214 2 215 >>> next(g) 216 Traceback (most recent call last): 217 File "<pyshell#37>", line 1, in -toplevel- 218 next(g) 219 File "<pyshell#35>", line 1, in <generator expression> 220 g = (10 // i for i in (5, 0, 2)) 221 ZeroDivisionError: integer division or modulo by zero 222 >>> next(g) 223 Traceback (most recent call last): 224 File "<pyshell#38>", line 1, in -toplevel- 225 next(g) 226 StopIteration 227 228Make sure that None is a valid return value 229 230 >>> list(None for i in range(10)) 231 [None, None, None, None, None, None, None, None, None, None] 232 233Check that generator attributes are present 234 235 >>> g = (i*i for i in range(3)) 236 >>> expected = set(['gi_frame', 'gi_running']) 237 >>> set(attr for attr in dir(g) if not attr.startswith('__')) >= expected 238 True 239 240 >>> from test.support import HAVE_DOCSTRINGS 241 >>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') 242 Implement next(self). 243 >>> import types 244 >>> isinstance(g, types.GeneratorType) 245 True 246 247Check the __iter__ slot is defined to return self 248 249 >>> iter(g) is g 250 True 251 252Verify that the running flag is set properly 253 254 >>> g = (me.gi_running for i in (0,1)) 255 >>> me = g 256 >>> me.gi_running 257 0 258 >>> next(me) 259 1 260 >>> me.gi_running 261 0 262 263Verify that genexps are weakly referencable 264 265 >>> import weakref 266 >>> g = (i*i for i in range(4)) 267 >>> wr = weakref.ref(g) 268 >>> wr() is g 269 True 270 >>> p = weakref.proxy(g) 271 >>> list(p) 272 [0, 1, 4, 9] 273 274 275""" 276 277import sys 278 279# Trace function can throw off the tuple reuse test. 280if hasattr(sys, 'gettrace') and sys.gettrace(): 281 __test__ = {} 282else: 283 __test__ = {'doctests' : doctests} 284 285def test_main(verbose=None): 286 from test import support 287 from test import test_genexps 288 support.run_doctest(test_genexps, verbose) 289 290 # verify reference counting 291 if verbose and hasattr(sys, "gettotalrefcount"): 292 import gc 293 counts = [None] * 5 294 for i in range(len(counts)): 295 support.run_doctest(test_genexps, verbose) 296 gc.collect() 297 counts[i] = sys.gettotalrefcount() 298 print(counts) 299 300if __name__ == "__main__": 301 test_main(verbose=True) 302