Python 3.10's fourth alpha makes unequal zip inputs explicit
Python 3.10.0a4 previews a length check for zip. Its design preserves lazy iteration while exposing a common source of silent data loss.
Pairing two streams of values often carries an assumption that both streams contain the same number of items. Python's zip has never required that assumption to hold. It stops when the shortest input ends, so a malformed input can produce a plausible partial result. Python 3.10.0a4, released on January 4, includes the optional length check specified by PEP 618. That change makes an existing correctness requirement expressible at the call site.
The fourth alpha remains a developer preview. The release announcement explicitly discourages production use, and features can still change during development. Its value here is that maintainers can examine the behavior of an ordinary built-in before applications depend on it. A small addition to an established operation deserves that attention because existing code already assigns meaning to its stopping behavior.
I appreciate this particular change because Python's iteration protocols are one of the language's strongest design choices. Libraries can participate in ordinary language operations without requiring a separate control structure for each container. That convenience becomes less attractive when a protocol quietly discards evidence that its caller supplied inconsistent data.
The error hidden by successful iteration
PEP 618, written by Brandt Bucher, identifies silent truncation as the central problem. Its examples include parallel data processing and transposing nested iterables. A rectangular collection can be transposed by unpacking its rows into zip, but an incomplete row shortens every resulting column. The operation completes successfully even though the assumed shape was wrong.
The proposal also documents a concrete defect in ast.literal_eval. An AST dictionary node with one key and no corresponding value could evaluate to an empty dictionary. The internal zip stopped before producing a pair, and the missing value disappeared from the result. That example matters because it connects the proposal to a real validation failure inside Python's own library. The difficulty is not confined to unfamiliar code or obscure iterator tricks.
Consider an application constructing a mapping from independently obtained names and values. The following example uses deliberately incomplete input.
names = ["timeout", "retries"]
values = [30]
partial = dict(zip(names, values))
# partial is {"timeout": 30}
complete = dict(zip(names, values, strict=True))
# Raises ValueError because the inputs have different lengths.
The first expression does exactly what ordinary zip promises. It provides no evidence that the application expected a second setting. With strict=True, the caller declares that unequal exhaustion is an error. The exception prevents the mapping constructor from returning a successful partial mapping, although it does not reverse any independent effects that occurred while producing the inputs.
A check performed during consumption
Strictness does not turn zip into an eager operation. PEP 618 specifies that the resulting iterator raises ValueError at the point where iteration would otherwise stop. Constructing the iterator therefore does not establish that its inputs have equal lengths. A consumer must reach the end for the check to finish.
That distinction matters when a loop performs work for every pair. Several valid pairs may reach the loop body before unequal exhaustion becomes visible. A caller that requires all-or-nothing processing needs to account for this timing. For finite inputs, collecting the paired values before applying effects is one possible design. That choice requires storage proportional to the collected result, so it changes the resource behavior of a streaming operation.
pairs = list(zip(names, values, strict=True))
# Processing starts only after pairing has completed successfully.
for name, value in pairs:
print(name, value)
The specification deliberately works with iterables. Requiring len on every input would exclude inputs that can yield values without reporting their length. Testing exhaustion keeps the operation compatible with that protocol. It also means strictness cannot promise to validate inputs that a consumer abandons early.
For a generator, reaching the end is an observable event. The reference algorithm obtains successive values and checks whether another iterator still has an item after one has stopped. Developers should therefore understand this as a consistency check during iteration, rather than a separate inspection of untouched inputs. An application that shares iterator objects between operations already needs to reason about which operation consumes each value.
Why the existing behavior remains useful
The proposal leaves the default unchanged. Ordinary zip remains useful when stopping with the shortest input is intentional, including combinations involving an infinite iterator. Making unequal lengths an unconditional error would change the meaning of those programs. The keyword lets callers select a stricter contract where the surrounding algorithm requires it.
PEP 618 considers adding itertools.zip_strict instead. It argues that modifying the familiar built-in makes the check easier to enable and avoids another largely duplicated implementation. It also distinguishes this operation from itertools.zip_longest, which fills missing positions and therefore solves a different data-processing problem. Rejecting a mismatch and manufacturing replacement values require different decisions from the caller.
I prefer the explicit keyword for equal-length data. The requirement remains visible beside the inputs, and readers do not have to infer it from a distant assertion. Its usefulness depends on choosing the operation that matches the algorithm, because truncation can still be the correct result.
For library maintainers examining this alpha, the most informative cases are existing zip calls whose surrounding code assumes a complete correspondence. The proposal's standard-library examples demonstrate that even mature code can conceal that requirement. Adding strictness at such a call makes the assumption executable while retaining the same iterable interface and the same successful pairs when the input is valid.