1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
import doctest
import unittest
doctests = """
Unpack tuple
>>> t = (1, 2, 3)
>>> a, b, c = t
>>> a == 1 and b == 2 and c == 3
True
Unpack list
>>> l = [4, 5, 6]
>>> a, b, c = l
>>> a == 4 and b == 5 and c == 6
True
Unpack dict
>>> d = {4: 'four', 5: 'five', 6: 'six'}
>>> a, b, c = d
>>> a == 4 and b == 5 and c == 6
True
Unpack implied tuple
>>> a, b, c = 7, 8, 9
>>> a == 7 and b == 8 and c == 9
True
Unpack string... fun!
>>> a, b, c = 'one'
>>> a == 'o' and b == 'n' and c == 'e'
True
Unpack generic sequence
>>> class Seq:
... def __getitem__(self, i):
... if i >= 0 and i < 3: return i
... raise IndexError
...
>>> a, b, c = Seq()
>>> a == 0 and b == 1 and c == 2
True
Single element unpacking, with extra syntax
>>> st = (99,)
>>> sl = [100]
>>> a, = st
>>> a
99
>>> b, = sl
>>> b
100
Now for some failures
Unpacking non-sequence
>>> a, b, c = 7
Traceback (most recent call last):
...
TypeError: cannot unpack non-iterable int object
Unpacking tuple of wrong size
>>> a, b = t
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 2, got 3)
Unpacking tuple of wrong size
>>> a, b = l
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 2, got 3)
Unpacking sequence too short
>>> a, b, c, d = Seq()
Traceback (most recent call last):
...
ValueError: not enough values to unpack (expected 4, got 3)
Unpacking sequence too long
>>> a, b = Seq()
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 2)
Unpacking a sequence where the test for too long raises a different kind of
error
>>> class BozoError(Exception):
... pass
...
>>> class BadSeq:
... def __getitem__(self, i):
... if i >= 0 and i < 3:
... return i
... elif i == 3:
... raise BozoError
... else:
... raise IndexError
...
Trigger code while not expecting an IndexError (unpack sequence too long, wrong
error)
>>> a, b, c, d, e = BadSeq()
Traceback (most recent call last):
...
test.test_unpack.BozoError
Trigger code while expecting an IndexError (unpack sequence too short, wrong
error)
>>> a, b, c = BadSeq()
Traceback (most recent call last):
...
test.test_unpack.BozoError
Allow unpacking empty iterables
>>> () = []
>>> [] = ()
>>> [] = []
>>> () = ()
Unpacking non-iterables should raise TypeError
>>> () = 42
Traceback (most recent call last):
...
TypeError: cannot unpack non-iterable int object
Unpacking to an empty iterable should raise ValueError
>>> () = [42]
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 0, got 1)
Unpacking a larger iterable should raise ValuleError, but it
should not entirely consume the iterable
>>> it = iter(range(100))
>>> x, y, z = it
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 3)
>>> next(it)
4
Unpacking unbalanced dict
>>> d = {4: 'four', 5: 'five', 6: 'six', 7: 'seven'}
>>> a, b, c = d
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 3, got 4)
Ensure that custom `__len__()` is NOT called when showing the error message
>>> class LengthTooLong:
... def __len__(self):
... return 5
... def __getitem__(self, i):
... return i*2
...
>>> x, y, z = LengthTooLong()
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 3)
For evil cases like these as well, no actual count to be shown
>>> class BadLength:
... def __len__(self):
... return 1
... def __getitem__(self, i):
... return i*2
...
>>> x, y, z = BadLength()
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 3)
"""
__test__ = {'doctests' : doctests}
def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite())
return tests
class TestCornerCases(unittest.TestCase):
def test_extended_oparg_not_ignored(self):
# https://github.com/python/cpython/issues/91625
target = "(" + "y,"*400 + ")"
code = f"""def unpack_400(x):
{target} = x
return y
"""
ns = {}
exec(code, ns)
unpack_400 = ns["unpack_400"]
# Warm up the function for quickening (PEP 659)
for _ in range(30):
y = unpack_400(range(400))
self.assertEqual(y, 399)
if __name__ == "__main__":
unittest.main()
|