mirror of
https://github.com/python/cpython.git
synced 2025-10-04 06:06:44 +00:00

bpo-27286 fixed a problem where BUILD_MAP_UNPACK_WITH_CALL could be emitted with an incorrect oparg value, causing the eval loop to access the wrong stack entry when attempting to read the function name. The associated magic number change caused significant problems when attempting to upgrade to 3.5.3 for anyone that relies on pre-cached bytecode remaining valid across maintenance releases. This patch restores the ability to import legacy bytecode generated by 3.5.0, 3.5.1 or 3.5.2, and modifies the eval loop to avoid any harmful consequences from the potentially malformed legacy bytecode. Original import patch by Petr Viktorin, eval loop patch by Serhiy Storchaka, and tests and integration by Nick Coghlan.
366 lines
8.7 KiB
Python
366 lines
8.7 KiB
Python
# Tests for extended unpacking, starred expressions.
|
|
|
|
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 = l
|
|
>>> a == 4 and b == [5, 6]
|
|
True
|
|
|
|
Unpack implied tuple
|
|
|
|
>>> *a, = 7, 8, 9
|
|
>>> a == [7, 8, 9]
|
|
True
|
|
|
|
Unpack string... fun!
|
|
|
|
>>> a, *b = 'one'
|
|
>>> a == 'o' and b == ['n', 'e']
|
|
True
|
|
|
|
Unpack long sequence
|
|
|
|
>>> a, b, c, *d, e, f, g = range(10)
|
|
>>> (a, b, c, d, e, f, g) == (0, 1, 2, [3, 4, 5, 6], 7, 8, 9)
|
|
True
|
|
|
|
Unpack short sequence
|
|
|
|
>>> a, *b, c = (1, 2)
|
|
>>> a == 1 and c == 2 and b == []
|
|
True
|
|
|
|
Unpack generic sequence
|
|
|
|
>>> class Seq:
|
|
... def __getitem__(self, i):
|
|
... if i >= 0 and i < 3: return i
|
|
... raise IndexError
|
|
...
|
|
>>> a, *b = Seq()
|
|
>>> a == 0 and b == [1, 2]
|
|
True
|
|
|
|
Unpack in for statement
|
|
|
|
>>> for a, *b, c in [(1,2,3), (4,5,6,7)]:
|
|
... print(a, b, c)
|
|
...
|
|
1 [2] 3
|
|
4 [5, 6] 7
|
|
|
|
Unpack in list
|
|
|
|
>>> [a, *b, c] = range(5)
|
|
>>> a == 0 and b == [1, 2, 3] and c == 4
|
|
True
|
|
|
|
Multiple targets
|
|
|
|
>>> a, *b, c = *d, e = range(5)
|
|
>>> a == 0 and b == [1, 2, 3] and c == 4 and d == [0, 1, 2, 3] and e == 4
|
|
True
|
|
|
|
Assignment unpacking
|
|
|
|
>>> a, b, *c = range(5)
|
|
>>> a, b, c
|
|
(0, 1, [2, 3, 4])
|
|
>>> *a, b, c = a, b, *c
|
|
>>> a, b, c
|
|
([0, 1, 2], 3, 4)
|
|
|
|
Set display element unpacking
|
|
|
|
>>> a = [1, 2, 3]
|
|
>>> sorted({1, *a, 0, 4})
|
|
[0, 1, 2, 3, 4]
|
|
|
|
>>> {1, *1, 0, 4}
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: 'int' object is not iterable
|
|
|
|
Dict display element unpacking
|
|
|
|
>>> kwds = {'z': 0, 'w': 12}
|
|
>>> sorted({'x': 1, 'y': 2, **kwds}.items())
|
|
[('w', 12), ('x', 1), ('y', 2), ('z', 0)]
|
|
|
|
>>> sorted({**{'x': 1}, 'y': 2, **{'z': 3}}.items())
|
|
[('x', 1), ('y', 2), ('z', 3)]
|
|
|
|
>>> sorted({**{'x': 1}, 'y': 2, **{'x': 3}}.items())
|
|
[('x', 3), ('y', 2)]
|
|
|
|
>>> sorted({**{'x': 1}, **{'x': 3}, 'x': 4}.items())
|
|
[('x', 4)]
|
|
|
|
>>> {**{}}
|
|
{}
|
|
|
|
>>> a = {}
|
|
>>> {**a}[0] = 1
|
|
>>> a
|
|
{}
|
|
|
|
>>> {**1}
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: 'int' object is not a mapping
|
|
|
|
>>> {**[]}
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: 'list' object is not a mapping
|
|
|
|
>>> len(eval("{" + ", ".join("**{{{}: {}}}".format(i, i)
|
|
... for i in range(1000)) + "}"))
|
|
1000
|
|
|
|
>>> {0:1, **{0:2}, 0:3, 0:4}
|
|
{0: 4}
|
|
|
|
List comprehension element unpacking
|
|
|
|
>>> a, b, c = [0, 1, 2], 3, 4
|
|
>>> [*a, b, c]
|
|
[0, 1, 2, 3, 4]
|
|
|
|
>>> l = [a, (3, 4), {5}, {6: None}, (i for i in range(7, 10))]
|
|
>>> [*item for item in l]
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: iterable unpacking cannot be used in comprehension
|
|
|
|
>>> [*[0, 1] for i in range(10)]
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: iterable unpacking cannot be used in comprehension
|
|
|
|
>>> [*'a' for i in range(10)]
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: iterable unpacking cannot be used in comprehension
|
|
|
|
>>> [*[] for i in range(10)]
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: iterable unpacking cannot be used in comprehension
|
|
|
|
Generator expression in function arguments
|
|
|
|
>>> list(*x for x in (range(5) for i in range(3)))
|
|
Traceback (most recent call last):
|
|
...
|
|
list(*x for x in (range(5) for i in range(3)))
|
|
^
|
|
SyntaxError: invalid syntax
|
|
|
|
>>> dict(**x for x in [{1:2}])
|
|
Traceback (most recent call last):
|
|
...
|
|
dict(**x for x in [{1:2}])
|
|
^
|
|
SyntaxError: invalid syntax
|
|
|
|
Iterable argument unpacking
|
|
|
|
>>> print(*[1], *[2], 3)
|
|
1 2 3
|
|
|
|
Make sure that they don't corrupt the passed-in dicts.
|
|
|
|
>>> def f(x, y):
|
|
... print(x, y)
|
|
...
|
|
>>> original_dict = {'x': 1}
|
|
>>> f(**original_dict, y=2)
|
|
1 2
|
|
>>> original_dict
|
|
{'x': 1}
|
|
|
|
Now for some failures
|
|
|
|
Make sure the raised errors are right for keyword argument unpackings
|
|
|
|
>>> from collections.abc import MutableMapping
|
|
>>> class CrazyDict(MutableMapping):
|
|
... def __init__(self):
|
|
... self.d = {}
|
|
...
|
|
... def __iter__(self):
|
|
... for x in self.d.__iter__():
|
|
... if x == 'c':
|
|
... self.d['z'] = 10
|
|
... yield x
|
|
...
|
|
... def __getitem__(self, k):
|
|
... return self.d[k]
|
|
...
|
|
... def __len__(self):
|
|
... return len(self.d)
|
|
...
|
|
... def __setitem__(self, k, v):
|
|
... self.d[k] = v
|
|
...
|
|
... def __delitem__(self, k):
|
|
... del self.d[k]
|
|
...
|
|
>>> d = CrazyDict()
|
|
>>> d.d = {chr(ord('a') + x): x for x in range(5)}
|
|
>>> e = {**d}
|
|
Traceback (most recent call last):
|
|
...
|
|
RuntimeError: dictionary changed size during iteration
|
|
|
|
>>> d.d = {chr(ord('a') + x): x for x in range(5)}
|
|
>>> def f(**kwargs): print(kwargs)
|
|
>>> f(**d)
|
|
Traceback (most recent call last):
|
|
...
|
|
RuntimeError: dictionary changed size during iteration
|
|
|
|
Overridden parameters
|
|
|
|
>>> f(x=5, **{'x': 3}, y=2)
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: f() got multiple values for keyword argument 'x'
|
|
|
|
>>> f(**{'x': 3}, x=5, y=2)
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: f() got multiple values for keyword argument 'x'
|
|
|
|
>>> f(**{'x': 3}, **{'x': 5}, y=2)
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: f() got multiple values for keyword argument 'x'
|
|
|
|
>>> f(x=5, **{'x': 3}, **{'x': 2})
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: function got multiple values for keyword argument 'x'
|
|
|
|
>>> f(**{1: 3}, **{1: 5})
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: f() keywords must be strings
|
|
|
|
Unpacking non-sequence
|
|
|
|
>>> a, *b = 7
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: 'int' object is not iterable
|
|
|
|
Unpacking sequence too short
|
|
|
|
>>> a, *b, c, d, e = Seq()
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: not enough values to unpack (expected at least 4, got 3)
|
|
|
|
Unpacking sequence too short and target appears last
|
|
|
|
>>> a, b, c, d, *e = Seq()
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: not enough values to unpack (expected at least 4, got 3)
|
|
|
|
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_ex.BozoError
|
|
|
|
Now some general starred expressions (all fail).
|
|
|
|
>>> a, *b, c, *d, e = range(10) # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: two starred expressions in assignment
|
|
|
|
>>> [*b, *c] = range(10) # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: two starred expressions in assignment
|
|
|
|
>>> *a = range(10) # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: starred assignment target must be in a list or tuple
|
|
|
|
>>> *a # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: can't use starred expression here
|
|
|
|
>>> *1 # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: can't use starred expression here
|
|
|
|
>>> x = *a # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: can't use starred expression here
|
|
|
|
Some size constraints (all fail.)
|
|
|
|
>>> s = ", ".join("a%d" % i for i in range(1<<8)) + ", *rest = range(1<<8 + 1)"
|
|
>>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: too many expressions in star-unpacking assignment
|
|
|
|
>>> s = ", ".join("a%d" % i for i in range(1<<8 + 1)) + ", *rest = range(1<<8 + 2)"
|
|
>>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
SyntaxError: too many expressions in star-unpacking assignment
|
|
|
|
(there is an additional limit, on the number of expressions after the
|
|
'*rest', but it's 1<<24 and testing it takes too much memory.)
|
|
|
|
"""
|
|
|
|
__test__ = {'doctests' : doctests}
|
|
|
|
def test_main(verbose=False):
|
|
import sys
|
|
from test import support
|
|
from test import test_unpack_ex
|
|
support.run_doctest(test_unpack_ex, verbose)
|
|
|
|
if __name__ == "__main__":
|
|
test_main(verbose=True)
|