Repository navigation
API consistency: no StopIteration from terminals; filter_map, unique, min_max and more - #23
Merged
Merged
Conversation
…in_max and more A StopIteration escaping a map/filter callback silently ends the enclosing iteration, so e.g. Itr(groups).map(lambda g: Itr(g).reduce(max)) dropped every group after the first empty one without error. Terminal methods now report a missing item without StopIteration: - position returns None when nothing matches (like find, and Rust's Option) - peek, nth (out of range) and reduce (empty) raise ValueError, consistent with last/max/min; next() keeps StopIteration, as the builtin does partition now calls the predicate once per item, buffering items for the side not being read, instead of filtering a copy twice. New: filter_map, find_map, unique, min_max, compress, take_last, and a repr. Extended: chain/interleave take *others, zip(strict=), groupby(sort=False), optional sorted_by key, all/any default to truthiness, sum/prod start. flat_map uses chain.from_iterable. enumerate gains a doctest and test: it was previously covered only via the old position implementation. SKILL.md, README, relnotes, apidoc and the examples notebook are updated to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow the builtins these methods stand in for, rather than using ValueError across the board: - nth past the end raises IndexError, as indexing a sequence does (a negative n remains a ValueError, as for itertools.islice) - reduce on an empty iterator raises TypeError, as functools.reduce does peek keeps ValueError: it has no builtin counterpart, and matches last/max/min. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The notebook demonstrated 39 of the 66 public methods. Add short examples for the other 27, under the existing section headings: count/last/take_last, zip_longest, pairwise, chunk_by, filter_map, compress, accumulate/scan, dedup/dedup_with_count/unique, sorted_by, sum/prod/min/min_max, value_counts, find/position/find_map, eq, is_sorted, map_while and peek/next_if. Also show the new chain and interleave variadics and groupby(sort=False). Fix statements that were wrong: - groupby was described as grouping consecutive elements; it sorts first (that's chunk_by) - interleave was said to stop at the shorter input (not since 0.3.0) - all was said to always consume the entire iterator; it short-circuits - an overrunning skip was said to raise StopIteration; it's lazy and yields nothing - the nth(10) comment still assumed 1-based indexing Includes a note that running the notebook needs ipykernel. All cells re-executed, so outputs are current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A
StopIterationescaping amap/filtercallback silently ends the enclosing iteration instead of raising. Several terminal methods raisedStopIterationfor a missing item, so an innocent-looking chain could drop data without any error:Breaking changes
positionreturnsNonewhen nothing matches, consistent withfind(and Rust'sOption).nthpast the end raisesIndexError, as indexing a sequence does.reduceon empty input raisesTypeError, asfunctools.reducedoes.peekraisesValueError; it has no builtin counterpart, so this matcheslast,maxandmin.next()deliberately still raisesStopIteration, like the builtin. The README and skill now warn against calling it inside callbacks.partitioncalls the predicate once per item (it was twice), buffering items for whichever side isn't being read. It is still lazy and still works on infinite input.Additions (non-breaking)
filter_map,find_map,unique(key=None)(lazy and order-preserving),min_max(key=None),compress,take_last(n).chain(*others)andinterleave(*others)take any number of iterables.zip(..., strict=),groupby(..., sort=False), optionalsorted_bykey,all()/any()default to truthiness,sum(start=)/prod(start=).Itr.__repr__shows the wrapped iterator without consuming it.flat_mapnow useschain.from_iterable.Also
enumerategains a doctest and a test. It had been covered only indirectly through the oldpositionimplementation, so changingpositionexposed the gap.doc/apidoc.md(regenerated) and the examples notebook are updated. The notebook caught an out-of-rangenthwithsuppress(StopIteration).groupbywas described as grouping consecutive runs, which ischunk_by, andallas never short-circuiting. All cells have been re-executed.Testing
ruff check,ruff format --check,ty check srcandpytestall pass with 100% coverage (264 tests) locally on 3.14;pytestalso passes on 3.12 and 3.13. There are new tests for each behaviour change, including a regression test for the silent truncation.🤖 Generated with Claude Code