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