Skip to content

Commit

Permalink
bpo-40636: Documentation for zip-strict (python#20961)
Browse files Browse the repository at this point in the history
  • Loading branch information
cool-RR committed Jun 19, 2020
1 parent 3358da4 commit 59cf853
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 44 deletions.
128 changes: 84 additions & 44 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1720,50 +1720,90 @@ are always available. They are listed here in alphabetical order.
dictionary are ignored.


.. function:: zip(*iterables)

Make an iterator that aggregates elements from each of the iterables.

Returns an iterator of tuples, where the *i*-th tuple contains
the *i*-th element from each of the argument sequences or iterables. The
iterator stops when the shortest input iterable is exhausted. With a single
iterable argument, it returns an iterator of 1-tuples. With no arguments,
it returns an empty iterator. Equivalent to::

def zip(*iterables):
# zip('ABCD', 'xy') --> Ax By
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result = []
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
return
result.append(elem)
yield tuple(result)

The left-to-right evaluation order of the iterables is guaranteed. This
makes possible an idiom for clustering a data series into n-length groups
using ``zip(*[iter(s)]*n)``. This repeats the *same* iterator ``n`` times
so that each output tuple has the result of ``n`` calls to the iterator.
This has the effect of dividing the input into n-length chunks.

:func:`zip` should only be used with unequal length inputs when you don't
care about trailing, unmatched values from the longer iterables. If those
values are important, use :func:`itertools.zip_longest` instead.

:func:`zip` in conjunction with the ``*`` operator can be used to unzip a
list::

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> zipped = zip(x, y)
>>> list(zipped)
[(1, 4), (2, 5), (3, 6)]
>>> x2, y2 = zip(*zip(x, y))
>>> x == list(x2) and y == list(y2)
True
.. function:: zip(*iterables, strict=False)

Iterate over several iterables in parallel, producing tuples with an item
from each one.

Example::

>>> for item in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']):
... print(item)
...
(1, 'sugar')
(2, 'spice')
(3, 'everything nice')

More formally: :func:`zip` returns an iterator of tuples, where the *i*-th
tuple contains the *i*-th element from each of the argument iterables.

Another way to think of :func:`zip` is that it turns rows into columns, and
columns into rows. This is similar to `transposing a matrix
<https://en.wikipedia.org/wiki/Transpose>`_.

:func:`zip` is lazy: The elements won't be processed until the iterable is
iterated on, e.g. by a :keyword:`!for` loop or by wrapping in a
:class:`list`.

One thing to consider is that the iterables passed to :func:`zip` could have
different lengths; sometimes by design, and sometimes because of a bug in
the code that prepared these iterables. Python offers three different
approaches to dealing with this issue:

* By default, :func:`zip` stops when the shortest iterable is exhausted.
It will ignore the remaining items in the longer iterables, cutting off
the result to the length of the shortest iterable::

>>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum']))
[(0, 'fee'), (1, 'fi'), (2, 'fo')]

* :func:`zip` is often used in cases where the iterables are assumed to be
of equal length. In such cases, it's recommended to use the ``strict=True``
option. Its output is the same as regular :func:`zip`::

>>> list(zip(('a', 'b', 'c'), (1, 2, 3), strict=True))
[('a', 1), ('b', 2), ('c', 3)]

Unlike the default behavior, it checks that the lengths of iterables are
identical, raising a :exc:`ValueError` if they aren't:

>>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum'], strict=True))
Traceback (most recent call last):
...
ValueError: zip() argument 2 is longer than argument 1

Without the ``strict=True`` argument, any bug that results in iterables of
different lengths will be silenced, possibly mainfesting as a hard-to-find
bug in another part of the program.

* Shorter iterables can be padded with a constant value to make all the
iterables have the same length. This is done by
:func:`itertools.zip_longest`.

Edge cases: With a single iterable argument, :func:`zip` returns an
iterator of 1-tuples. With no arguments, it returns an empty iterator.

Tips and tricks:

* The left-to-right evaluation order of the iterables is guaranteed. This
makes possible an idiom for clustering a data series into n-length groups
using ``zip(*[iter(s)]*n, strict=True)``. This repeats the *same* iterator
``n`` times so that each output tuple has the result of ``n`` calls to the
iterator. This has the effect of dividing the input into n-length chunks.

* :func:`zip` in conjunction with the ``*`` operator can be used to unzip a
list::

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> list(zip(x, y))
[(1, 4), (2, 5), (3, 6)]
>>> x2, y2 = zip(*zip(x, y))
>>> x == list(x2) and y == list(y2)
True

.. versionchanged:: 3.10
Added the ``strict`` argument.


.. function:: __import__(name, globals=None, locals=None, fromlist=(), level=0)
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ New Features
:class:`types.MappingProxyType` object wrapping the original
dictionary. (Contributed by Dennis Sweeney in :issue:`40890`.)

* :pep:`618`: The :func:`zip` function now has an optional ``strict`` flag, used
to require that all the iterables have an equal length.


Other Language Changes
======================
Expand Down

0 comments on commit 59cf853

Please sign in to comment.