Skip to content

Commit

Permalink
gh-122239: Add actual count in unbalanced unpacking error message whe…
Browse files Browse the repository at this point in the history
…n possible (#122244)
  • Loading branch information
tusharsadhwani committed Sep 10, 2024
1 parent 07f0bf5 commit 3597642
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 3 deletions.
15 changes: 15 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ Summary -- Release highlights
New Features
============

Improved Error Messages
-----------------------

* When unpacking assignment fails due to incorrect number of variables, the
error message prints the received number of values in more cases than before.
(Contributed by Tushar Sadhwani in :gh:`122239`.)

.. code-block:: pycon
>>> x, y, z = 1, 2, 3, 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
x, y, z = 1, 2, 3, 4
^^^^^^^
ValueError: too many values to unpack (expected 3, got 4)
Other Language Changes
Expand Down
57 changes: 54 additions & 3 deletions Lib/test/test_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
>>> 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
Expand Down Expand Up @@ -66,14 +73,14 @@
>>> a, b = t
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 2)
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)
ValueError: too many values to unpack (expected 2, got 3)
Unpacking sequence too short
Expand Down Expand Up @@ -140,8 +147,52 @@
>>> () = [42]
Traceback (most recent call last):
...
ValueError: too many values to unpack (expected 0)
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}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
When a :class:`list`, :class:`tuple` or :class:`dict`
with too many elements is unpacked, show the actual
length in the error message.
11 changes: 11 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2148,6 +2148,17 @@ _PyEval_UnpackIterableStackRef(PyThreadState *tstate, _PyStackRef v_stackref,
return 1;
}
Py_DECREF(w);

if (PyList_CheckExact(v) || PyTuple_CheckExact(v)
|| PyDict_CheckExact(v)) {
ll = PyDict_CheckExact(v) ? PyDict_Size(v) : Py_SIZE(v);
if (ll > argcnt) {
_PyErr_Format(tstate, PyExc_ValueError,
"too many values to unpack (expected %d, got %zd)",
argcnt, ll);
goto Error;
}
}
_PyErr_Format(tstate, PyExc_ValueError,
"too many values to unpack (expected %d)",
argcnt);
Expand Down

0 comments on commit 3597642

Please sign in to comment.