1 # Tests for extended unpacking, starred expressions.
2
3 import doctest
4 import unittest
5
6
7 doctests = """
8
9 Unpack tuple
10
11 >>> t = (1, 2, 3)
12 >>> a, *b, c = t
13 >>> a == 1 and b == [2] and c == 3
14 True
15
16 Unpack list
17
18 >>> l = [4, 5, 6]
19 >>> a, *b = l
20 >>> a == 4 and b == [5, 6]
21 True
22
23 Unpack implied tuple
24
25 >>> *a, = 7, 8, 9
26 >>> a == [7, 8, 9]
27 True
28
29 Unpack string... fun!
30
31 >>> a, *b = 'one'
32 >>> a == 'o' and b == ['n', 'e']
33 True
34
35 Unpack long sequence
36
37 >>> a, b, c, *d, e, f, g = range(10)
38 >>> (a, b, c, d, e, f, g) == (0, 1, 2, [3, 4, 5, 6], 7, 8, 9)
39 True
40
41 Unpack short sequence
42
43 >>> a, *b, c = (1, 2)
44 >>> a == 1 and c == 2 and b == []
45 True
46
47 Unpack generic sequence
48
49 >>> class Seq:
50 ... def __getitem__(self, i):
51 ... if i >= 0 and i < 3: return i
52 ... raise IndexError
53 ...
54 >>> a, *b = Seq()
55 >>> a == 0 and b == [1, 2]
56 True
57
58 Unpack in for statement
59
60 >>> for a, *b, c in [(1,2,3), (4,5,6,7)]:
61 ... print(a, b, c)
62 ...
63 1 [2] 3
64 4 [5, 6] 7
65
66 Unpack in list
67
68 >>> [a, *b, c] = range(5)
69 >>> a == 0 and b == [1, 2, 3] and c == 4
70 True
71
72 Multiple targets
73
74 >>> a, *b, c = *d, e = range(5)
75 >>> a == 0 and b == [1, 2, 3] and c == 4 and d == [0, 1, 2, 3] and e == 4
76 True
77
78 Assignment unpacking
79
80 >>> a, b, *c = range(5)
81 >>> a, b, c
82 (0, 1, [2, 3, 4])
83 >>> *a, b, c = a, b, *c
84 >>> a, b, c
85 ([0, 1, 2], 3, 4)
86
87 Set display element unpacking
88
89 >>> a = [1, 2, 3]
90 >>> sorted({1, *a, 0, 4})
91 [0, 1, 2, 3, 4]
92
93 >>> {1, *1, 0, 4}
94 Traceback (most recent call last):
95 ...
96 TypeError: 'int' object is not iterable
97
98 Dict display element unpacking
99
100 >>> kwds = {'z': 0, 'w': 12}
101 >>> sorted({'x': 1, 'y': 2, **kwds}.items())
102 [('w', 12), ('x', 1), ('y', 2), ('z', 0)]
103
104 >>> sorted({**{'x': 1}, 'y': 2, **{'z': 3}}.items())
105 [('x', 1), ('y', 2), ('z', 3)]
106
107 >>> sorted({**{'x': 1}, 'y': 2, **{'x': 3}}.items())
108 [('x', 3), ('y', 2)]
109
110 >>> sorted({**{'x': 1}, **{'x': 3}, 'x': 4}.items())
111 [('x', 4)]
112
113 >>> {**{}}
114 {}
115
116 >>> a = {}
117 >>> {**a}[0] = 1
118 >>> a
119 {}
120
121 >>> {**1}
122 Traceback (most recent call last):
123 ...
124 TypeError: 'int' object is not a mapping
125
126 >>> {**[]}
127 Traceback (most recent call last):
128 ...
129 TypeError: 'list' object is not a mapping
130
131 >>> len(eval("{" + ", ".join("**{{{}: {}}}".format(i, i)
132 ... for i in range(1000)) + "}"))
133 1000
134
135 >>> {0:1, **{0:2}, 0:3, 0:4}
136 {0: 4}
137
138 List comprehension element unpacking
139
140 >>> a, b, c = [0, 1, 2], 3, 4
141 >>> [*a, b, c]
142 [0, 1, 2, 3, 4]
143
144 >>> l = [a, (3, 4), {5}, {6: None}, (i for i in range(7, 10))]
145 >>> [*item for item in l]
146 Traceback (most recent call last):
147 ...
148 SyntaxError: iterable unpacking cannot be used in comprehension
149
150 >>> [*[0, 1] for i in range(10)]
151 Traceback (most recent call last):
152 ...
153 SyntaxError: iterable unpacking cannot be used in comprehension
154
155 >>> [*'a' for i in range(10)]
156 Traceback (most recent call last):
157 ...
158 SyntaxError: iterable unpacking cannot be used in comprehension
159
160 >>> [*[] for i in range(10)]
161 Traceback (most recent call last):
162 ...
163 SyntaxError: iterable unpacking cannot be used in comprehension
164
165 >>> {**{} for a in [1]}
166 Traceback (most recent call last):
167 ...
168 SyntaxError: dict unpacking cannot be used in dict comprehension
169
170 # Pegen is better here.
171 # Generator expression in function arguments
172
173 # >>> list(*x for x in (range(5) for i in range(3)))
174 # Traceback (most recent call last):
175 # ...
176 # list(*x for x in (range(5) for i in range(3)))
177 # ^
178 # SyntaxError: invalid syntax
179
180 >>> dict(**x for x in [{1:2}])
181 Traceback (most recent call last):
182 ...
183 dict(**x for x in [{1:2}])
184 ^
185 SyntaxError: invalid syntax
186
187 Iterable argument unpacking
188
189 >>> print(*[1], *[2], 3)
190 1 2 3
191
192 Make sure that they don't corrupt the passed-in dicts.
193
194 >>> def f(x, y):
195 ... print(x, y)
196 ...
197 >>> original_dict = {'x': 1}
198 >>> f(**original_dict, y=2)
199 1 2
200 >>> original_dict
201 {'x': 1}
202
203 Now for some failures
204
205 Make sure the raised errors are right for keyword argument unpackings
206
207 >>> from collections.abc import MutableMapping
208 >>> class CrazyDict(MutableMapping):
209 ... def __init__(self):
210 ... self.d = {}
211 ...
212 ... def __iter__(self):
213 ... for x in self.d.__iter__():
214 ... if x == 'c':
215 ... self.d['z'] = 10
216 ... yield x
217 ...
218 ... def __getitem__(self, k):
219 ... return self.d[k]
220 ...
221 ... def __len__(self):
222 ... return len(self.d)
223 ...
224 ... def __setitem__(self, k, v):
225 ... self.d[k] = v
226 ...
227 ... def __delitem__(self, k):
228 ... del self.d[k]
229 ...
230 >>> d = CrazyDict()
231 >>> d.d = {chr(ord('a') + x): x for x in range(5)}
232 >>> e = {**d}
233 Traceback (most recent call last):
234 ...
235 RuntimeError: dictionary changed size during iteration
236
237 >>> d.d = {chr(ord('a') + x): x for x in range(5)}
238 >>> def f(**kwargs): print(kwargs)
239 >>> f(**d)
240 Traceback (most recent call last):
241 ...
242 RuntimeError: dictionary changed size during iteration
243
244 Overridden parameters
245
246 >>> f(x=5, **{'x': 3}, y=2)
247 Traceback (most recent call last):
248 ...
249 TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
250
251 >>> f(**{'x': 3}, x=5, y=2)
252 Traceback (most recent call last):
253 ...
254 TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
255
256 >>> f(**{'x': 3}, **{'x': 5}, y=2)
257 Traceback (most recent call last):
258 ...
259 TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
260
261 >>> f(x=5, **{'x': 3}, **{'x': 2})
262 Traceback (most recent call last):
263 ...
264 TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
265
266 >>> f(**{1: 3}, **{1: 5})
267 Traceback (most recent call last):
268 ...
269 TypeError: test.test_unpack_ex.f() got multiple values for keyword argument '1'
270
271 Unpacking non-sequence
272
273 >>> a, *b = 7
274 Traceback (most recent call last):
275 ...
276 TypeError: cannot unpack non-iterable int object
277
278 Unpacking sequence too short
279
280 >>> a, *b, c, d, e = Seq()
281 Traceback (most recent call last):
282 ...
283 ValueError: not enough values to unpack (expected at least 4, got 3)
284
285 Unpacking sequence too short and target appears last
286
287 >>> a, b, c, d, *e = Seq()
288 Traceback (most recent call last):
289 ...
290 ValueError: not enough values to unpack (expected at least 4, got 3)
291
292 Unpacking a sequence where the test for too long raises a different kind of
293 error
294
295 >>> class BozoError(Exception):
296 ... pass
297 ...
298 >>> class BadSeq:
299 ... def __getitem__(self, i):
300 ... if i >= 0 and i < 3:
301 ... return i
302 ... elif i == 3:
303 ... raise BozoError
304 ... else:
305 ... raise IndexError
306 ...
307
308 Trigger code while not expecting an IndexError (unpack sequence too long, wrong
309 error)
310
311 >>> a, *b, c, d, e = BadSeq()
312 Traceback (most recent call last):
313 ...
314 test.test_unpack_ex.BozoError
315
316 Now some general starred expressions (all fail).
317
318 >>> a, *b, c, *d, e = range(10) # doctest:+ELLIPSIS
319 Traceback (most recent call last):
320 ...
321 SyntaxError: multiple starred expressions in assignment
322
323 >>> [*b, *c] = range(10) # doctest:+ELLIPSIS
324 Traceback (most recent call last):
325 ...
326 SyntaxError: multiple starred expressions in assignment
327
328 >>> a,*b,*c,*d = range(4) # doctest:+ELLIPSIS
329 Traceback (most recent call last):
330 ...
331 SyntaxError: multiple starred expressions in assignment
332
333 >>> *a = range(10) # doctest:+ELLIPSIS
334 Traceback (most recent call last):
335 ...
336 SyntaxError: starred assignment target must be in a list or tuple
337
338 >>> *a # doctest:+ELLIPSIS
339 Traceback (most recent call last):
340 ...
341 SyntaxError: can't use starred expression here
342
343 >>> *1 # doctest:+ELLIPSIS
344 Traceback (most recent call last):
345 ...
346 SyntaxError: can't use starred expression here
347
348 >>> x = *a # doctest:+ELLIPSIS
349 Traceback (most recent call last):
350 ...
351 SyntaxError: can't use starred expression here
352
353 >>> (*x),y = 1, 2 # doctest:+ELLIPSIS
354 Traceback (most recent call last):
355 ...
356 SyntaxError: cannot use starred expression here
357
358 >>> (((*x))),y = 1, 2 # doctest:+ELLIPSIS
359 Traceback (most recent call last):
360 ...
361 SyntaxError: cannot use starred expression here
362
363 >>> z,(*x),y = 1, 2, 4 # doctest:+ELLIPSIS
364 Traceback (most recent call last):
365 ...
366 SyntaxError: cannot use starred expression here
367
368 >>> z,(*x) = 1, 2 # doctest:+ELLIPSIS
369 Traceback (most recent call last):
370 ...
371 SyntaxError: cannot use starred expression here
372
373 >>> ((*x),y) = 1, 2 # doctest:+ELLIPSIS
374 Traceback (most recent call last):
375 ...
376 SyntaxError: cannot use starred expression here
377
378 Some size constraints (all fail.)
379
380 >>> s = ", ".join("a%d" % i for i in range(1<<8)) + ", *rest = range(1<<8 + 1)"
381 >>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS
382 Traceback (most recent call last):
383 ...
384 SyntaxError: too many expressions in star-unpacking assignment
385
386 >>> s = ", ".join("a%d" % i for i in range(1<<8 + 1)) + ", *rest = range(1<<8 + 2)"
387 >>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS
388 Traceback (most recent call last):
389 ...
390 SyntaxError: too many expressions in star-unpacking assignment
391
392 (there is an additional limit, on the number of expressions after the
393 '*rest', but it's 1<<24 and testing it takes too much memory.)
394
395 """
396
397 __test__ = {'doctests' : doctests}
398
399 def load_tests(loader, tests, pattern):
400 tests.addTest(doctest.DocTestSuite())
401 return tests
402
403
404 if __name__ == "__main__":
405 unittest.main()