On Sat, May 2, 2020 at 1:19 PM Steven D'Aprano <steve@pearwood.info> wrote:
But I question whether *enough* people need it *often enough* to make it
a builtin, or to put a flag on plain zip.
 
It has taken years for it to be added to more-itertools, suggesting that
the real world need for this is small.
 
My personal opinion is that given that Brandt has found one concrete use
for this in the stdlib

Many (I'd guess most, but it's hard to measure) uses of zip are concrete use cases for a strict zip. Most uses of zip are meant for equal lengths, and unequal lengths are a sign of an error somewhere. People just can't be bothered at the moment to make their zips strict because it's inconvenient.

I'm guessing Brandt's ast.unparse example is more significant than just a place where a strict check could be useful. I'm guessing it's an example where he or someone else actually got bit by the lack of a check. Those are hard to find. But I'm pretty sure I've experienced it, and we know Ram has.

Something that's a bit easier to find is examples where people have checked lengths directly. These either show that a strict zip could have been used directly, or at least that people are concerned about unequal lengths:

https://github.com/more-itertools/more-itertools/blob/master/more_itertools/more.py#L1456
    if len(iterables) != len(offsets):
        raise ValueError("Number of iterables and offsets didn't match")

    staggered = []
    for it, n in zip(iterables, offsets):
    
https://github.com/pypa/setuptools/blob/master/setuptools/dep_util.py#L13
    if len(sources_groups) != len(targets):
        raise ValueError("'sources_group' and 'targets' must be the same length")

    # build a pair of lists (sources_groups, targets) where source is newer
    n_sources = []
    n_targets = []
    for i in range(len(sources_groups)):
        if newer_group(sources_groups[i], targets[i]):

https://github.com/gristlabs/asttokens/blob/master/tests/test_mark_tokens.py#L783
      self.assertEqual(len(t1), len(t2))
      for vc1, vc2 in zip(t1, t2):
        self.assert_nodes_equal(vc1, vc2)

File "cpython/Tools/demo/sortvisu.py", line 348
    if len(oldpts) != len(newpts):
        raise ValueError("can't interpolate arrays of different length")
    pts = [0]*len(oldpts)
    res = [tuple(oldpts)]
    for i in range(1, n):
        for k in range(len(pts)):
            pts[k] = oldpts[k] + (newpts[k] - oldpts[k])*i//n

File "cpython/Modules/_decimal/libmpdec/literature/fnt.py", line 194
    assert(len(a) == len(b))
    x = ntt(a, 1)
    y = ntt(b, 1)
    for i in range(len(a)):
        y[i] = y[i] * x[i]

https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/collections.py#L1121
        if len(verts) != len(codes):
            raise ValueError("'codes' must be a 1D list or array "
                             "with the same length of 'verts'")
        self._paths = []
        for xy, cds in zip(verts, codes):

https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py#L1451
        if len(lineoffsets) != len(positions):
            raise ValueError('lineoffsets and positions are unequal sized '
                             'sequences')
        if len(linelengths) != len(positions):
            raise ValueError('linelengths and positions are unequal sized '
                             'sequences')
        if len(linewidths) != len(positions):
            raise ValueError('linewidths and positions are unequal sized '
                             'sequences')
        if len(colors) != len(positions):
            raise ValueError('colors and positions are unequal sized '
                             'sequences')
        if len(linestyles) != len(positions):
            raise ValueError('linestyles and positions are unequal sized '
                             'sequences')

        colls = []
        for position, lineoffset, linelength, linewidth, color, linestyle in \
                zip(positions, lineoffsets, linelengths, linewidths,
                    colors, linestyles):

https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py#L3412
            for e in [a, b]:
                if len(data) != len(e):
                    raise ValueError(
                        f"The lengths of the data ({len(data)}) and the "
                        f"error {len(e)} do not match")
            low = [v - e for v, e in zip(data, a)]
            high = [v + e for v, e in zip(data, b)]

https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py#L3783
            if (len(np.ravel(usermedians)) != len(bxpstats) or
                    np.shape(usermedians)[0] != len(bxpstats)):
                raise ValueError(
                    "'usermedians' and 'x' have different lengths")
            else:
                # reassign medians as necessary
                for stats, med in zip(bxpstats, usermedians):

https://github.com/cython/cython/blob/master/Cython/Tempita/_tempita.py#L270
                if len(vars) != len(item):
                    raise ValueError(
                        'Need %i items to unpack (got %i items)'
                        % (len(vars), len(item)))
                for name, value in zip(vars, item):

https://github.com/cython/cython/blob/master/Cython/Compiler/ExprNodes.py#L3968
        if len(specific_types) > len(fused_types):
            return error(self.pos, "Too many types specified")
        elif len(specific_types) < len(fused_types):
            t = fused_types[len(specific_types)]
            return error(self.pos, "Not enough types specified to specialize "
                                   "the function, %s is still fused" % t)

        # See if our index types form valid specializations
        for pos, specific_type, fused_type in zip(positions,
                                                  specific_types,