iterquarry
iterquarry is a Python library of extra tools for working with iterables. It sits beside the standard-library itertools module and covers the jobs that come up once a loop is no longer enough: fixed-size batches, sliding windows, lookahead, flattening nested data, sampling, combinatorics, and small numeric helpers.
The public functions are plain callables. Import the names you need:
from iterquarry import pairwise, chunked, windowed
list(pairwise("ABCDE"))
# [('A', 'B'), ('B', 'C'), ('C', 'D'), ('D', 'E')]
Version 2.2.1 requires Python 3.10 or newer and has no third-party dependencies. The license is MIT.
Installation
Install the release from PyPI:
pip install iterquarry
For a local checkout, install the package in editable mode together with the test extra:
pip install -e ".[test]"
What it is for
Most of the library yields values lazily. You can pass a generator, a file, or any other iterable, and work starts only when you iterate the result. A few tools need random access or a known length, and those accept a sequence or build a list internally. Each section below says when that happens.
Typical uses:
- Walk a sequence in pairs, triples, or fixed-width windows.
- Split a stream into batches, or split it where a predicate changes.
- Peek at the next item, or step back through items you have already seen.
- Flatten, interleave, zip, and sort several iterables together.
- Keep the first item, the last item, or exactly one item, and fail clearly when the count is wrong.
- Sample, count, group, and reduce without writing the bookkeeping by hand.
- Generate combinations, permutations, partitions, and prime-related sequences.
Quick start
from iterquarry import chunked, flatten, windowed
list(chunked(range(10), 3))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
list(windowed(range(1, 6), 3))
# [(1, 2, 3), (2, 3, 4), (3, 4, 5)]
list(flatten([[0, 1], [2, 3]]))
# [0, 1, 2, 3]
chunked collects each batch into a list. windowed yields tuples and pads a short final window when you pass fillvalue. flatten removes one level of nesting.
Grouping and splitting
chunked(iterable, n) cuts an iterable into lists of length n. The last list is shorter when the length does not divide evenly. Pass strict=True to raise ValueError on a short final batch. chunked(iterable, None) returns one list of every item.
chunked_even keeps the batch sizes as close as possible, with the longer batches first:
from iterquarry import chunked_even
list(chunked_even(range(7), 3))
# [[0, 1, 2], [3, 4], [5, 6]]
batched yields tuples and matches the strictness rules of itertools.batched on newer Python versions. grouper also builds fixed-width tuples. Its incomplete argument is "fill", "ignore", or "strict":
from iterquarry import grouper
list(grouper("ABCDEFG", 3, incomplete="fill", fillvalue="x"))
# [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
distribute deals items out one by one, the way you deal cards. divide cuts the iterable into contiguous blocks. divide reads the whole iterable first.
from iterquarry import distribute, divide
[list(part) for part in distribute(3, range(8))]
# [[0, 3, 6], [1, 4, 7], [2, 5]]
[list(part) for part in divide(3, range(8))]
# [[0, 1, 2], [3, 4, 5], [6, 7]]
split_before, split_after, split_at, and split_when break an iterable wherever a predicate says so. split_into uses an explicit list of sizes.
from iterquarry import split_before
[list(group) for group in split_before("abcdef", lambda char: char in "ce")]
# [['a', 'b'], ['c', 'd'], ['e', 'f']]
bucket files each item under a key and lets you read one key at a time:
from iterquarry import bucket
grouped = bucket("AbBa", key=str.lower)
list(grouped["a"])
# ['A', 'a']
list(grouped["b"])
# ['b', 'B']
unzip is the inverse of zip: one iterable of tuples becomes one iterator per column.
Other grouping tools: ichunked, sliced, constrained_batches, groupby_transform, consecutive_groups.
Windows
pairwise yields overlapping pairs. triplewise yields overlapping triples. sliding_window is the general form and yields a tuple of length n on every step.
from iterquarry import sliding_window, triplewise
list(triplewise(range(5)))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]
list(sliding_window(range(5), 3))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]
windowed is the padded variant. Set step to move ahead by more than one item, and set fillvalue when the tail is shorter than the window. windowed_complete yields (before, window, after) so you can see the items on both sides of each window.
stagger zips an iterable with offset copies of itself:
from iterquarry import stagger
list(stagger([1, 2, 3, 4, 5], offsets=(-1, 0, 1), longest=True))
# [(None, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, None), (5, None, None)]
substrings and subslices yield every non-empty contiguous slice. substrings_indexes yields (start, end) pairs for a sequence. adjacent marks each item when it, or a neighbor within distance, satisfies a predicate.
Lookahead
spy copies the first few items and returns both that copy and an iterator that still starts at the beginning:
from iterquarry import spy
head, remaining = spy("abcdef", 2)
head
# ['a', 'b']
list(remaining)
# ['a', 'b', 'c', 'd', 'e', 'f']
peekable wraps an iterator so you can inspect the next item without consuming it, and put items back with prepend:
from iterquarry import peekable
items = peekable("abc")
items.peek()
# 'a'
next(items)
# 'a'
list(items)
# ['b', 'c']
Indexing a peekable reads ahead as far as the index requires. A negative index reads the rest of the source.
seekable remembers the items it has produced and can return to an earlier position with seek. A maxlen limits how far back that cache reaches. relative_seek moves from the current position.
Combining iterables
flatten removes one level of nesting. collapse keeps going into nested iterables. Strings and bytes stay intact unless you change base_type. levels stops the collapse after a fixed depth.
from iterquarry import collapse
list(collapse([1, [2, [3, [4]]]]))
# [1, 2, 3, 4]
interleave takes one item from each iterable in turn and stops at the shortest. interleave_longest continues until every iterable is exhausted. roundrobin does the same with a different internal schedule. interleave_evenly spaces shorter iterables through the longest one. interleave_randomly picks among the iterables that still have items.
from iterquarry import interleave, roundrobin
list(interleave([1, 2, 3], [4, 5], [6]))
# [1, 4, 6]
list(roundrobin("ABC", "D", "EF"))
# ['A', 'D', 'E', 'B', 'F', 'C']
zip_offset shifts each iterable by a given offset before zipping. zip_broadcast repeats strings, bytes, and other scalars across the items of the real iterables. sort_together sorts several sequences by one or more columns and returns them in the new order:
from iterquarry import sort_together, zip_offset
list(zip_offset("0123", "abcdef", offsets=(0, 1)))
# [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')]
sort_together([["a", "b", "c"], [3, 1, 2]], key_list=(1,))
# (('b', 'c', 'a'), (1, 2, 3))
prepend adds one value at the front. value_chain yields its arguments, iterating anything that is not a string or bytes. partial_product walks a product space one coordinate at a time. dotproduct and transpose cover the small linear-algebra cases, and matmul multiplies two matrices given as iterables of rows.
Selecting items
first and last return one end of an iterable. take and tail return several items. nth returns the item at an index, or a default when the iterable ends first. nth_or_last returns the last item when the index runs past the end.
from iterquarry import first, last, tail, take
first([1, 2, 3])
# 1
last([1, 2, 3])
# 3
take(2, "abcde")
# ['a', 'b']
list(tail(2, "abcde"))
# ['d', 'e']
one requires exactly one item and raises ValueError when the iterable is empty or longer. only returns the single item, or a default when the iterable is empty, and raises when there is a second item. strictly_n yields exactly n items and calls too_short or too_long when the length differs.
unique_everseen drops items that have already appeared. unique_justseen drops only adjacent duplicates. unique sorts, then drops duplicates, so it accepts unhashable items. duplicates_everseen and duplicates_justseen yield the copies that the unique helpers would drop. unique_in_window treats an item as new when it has not appeared in the last n items.
locate yields indexes where a predicate is true. rlocate yields those indexes from the right. replace substitutes matching items, or matching windows when window_size is greater than one.
strip, lstrip, and rstrip drop leading or trailing items that match a predicate, the way str.strip drops characters. before_and_after splits once, at the first failing item, and returns both sides:
from iterquarry import before_and_after
head, rest = before_and_after(lambda value: value < 3, range(6))
list(head), list(rest)
# ([0, 1, 2], [3, 4, 5])
partition splits into the items that fail a predicate and the items that pass. filter_except and map_except skip items that raise one of the given exceptions. iter_suppress does the same while iterating. filter_map keeps values that are true after a function runs. extract pulls items at selected indexes. takewhile_inclusive keeps the first item that fails the predicate, then stops. longest_common_prefix walks several iterables until they disagree.
Summaries
These helpers reduce an iterable to a count, a flag, or a small aggregate.
from iterquarry import all_equal, ilen, quantify
ilen(range(5))
# 5
quantify([1, 0, 2, 0, 3])
# 3
all_equal([2, 2, 2])
# True
all_unique reports whether every item is distinct. is_sorted checks order, with strict=True when equal neighbors should fail. exactly_n is true when exactly n items satisfy a predicate. iequals compares several iterables item by item. minmax returns the smallest and largest value in one pass. argmin and argmax return the index of the extreme value.
sample draws k items. It supports weights, counts, and strict. The draw uses the random module, so successive calls differ. run_length.encode compresses adjacent repeats, and run_length.decode expands them:
from iterquarry import run_length
list(run_length.encode("aaabbca"))
# [('a', 3), ('b', 2), ('c', 1), ('a', 1)]
consecutive_groups groups values that increase by one. map_reduce groups by a key, maps each item, and optionally reduces each group. join_mappings merges dictionaries that share keys. unique_to_each lists the items that appear in only one of several iterables.
Adding information to a stream
mark_ends tags the first and last items:
from iterquarry import mark_ends
list(mark_ends("ABC"))
# [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')]
count_cycle pairs each item with a cycle counter. intersperse inserts a separator every n items. padded extends an iterable out to a length, or out to the next multiple of a length. repeat_each repeats every item. repeat_last yields the final item forever after the source ends. ncycles repeats the whole iterable n times. pad_none is an endless tail of None.
from iterquarry import intersperse, padded, repeat_each
list(intersperse("|", "abc"))
# ['a', '|', 'b', '|', 'c']
list(padded([1, 2], n=4, fillvalue=0))
# [1, 2, 0, 0]
list(repeat_each("AB", 2))
# ['A', 'A', 'B', 'B']
classify_unique yields (item, unique_just_seen, unique_ever_seen) so a single pass can answer both duplicate questions.
Combinatorics
powerset yields every subset, shortest first. distinct_permutations and distinct_combinations skip results that look the same because of repeated values. circular_shifts rotates a sequence. partitions splits a sequence into contiguous blocks. set_partitions splits it into unordered groups.
from iterquarry import circular_shifts, distinct_permutations, powerset, set_partitions
list(powerset("AB"))
# [(), ('A',), ('B',), ('A', 'B')]
list(distinct_permutations("AAB"))
# [('A', 'A', 'B'), ('A', 'B', 'A'), ('B', 'A', 'A')]
list(circular_shifts("ABCD"))
# [('A', 'B', 'C', 'D'), ('B', 'C', 'D', 'A'), ('C', 'D', 'A', 'B'), ('D', 'A', 'B', 'C')]
list(set_partitions("abc", 2))
# [[['a'], ['b', 'c']], [['a', 'b'], ['c']], [['b'], ['a', 'c']]]
gray_product walks a product so that only one coordinate changes at each step. derangements yields permutations with no item left in its original place. outer_product applies a function to every pair from two iterables. powerset_of_sets builds the power set with set objects.
When you need one result by index, use nth_combination, nth_combination_with_replacement, nth_permutation, or nth_product. The matching combination_index, combination_with_replacement_index, permutation_index, and product_index functions go the other way. random_product, random_permutation, random_combination, random_combination_with_replacement, and random_derangement each return one result.
from iterquarry import gray_product, nth_combination
list(gray_product("AB", "CD"))
# [('A', 'C'), ('B', 'C'), ('B', 'D'), ('A', 'D')]
nth_combination(range(5), 3, 5)
# (0, 3, 4)
Numeric helpers
numeric_range follows the same start, stop, and step pattern as range, for any numeric type that supports addition and comparison. That includes Decimal, Fraction, and datetime values. A datetime range needs an explicit timedelta step.
import datetime
from decimal import Decimal
from iterquarry import numeric_range
list(numeric_range(Decimal("1.0"), Decimal("2.0"), Decimal("0.3")))
# [Decimal('1.0'), Decimal('1.3'), Decimal('1.6'), Decimal('1.9')]
start = datetime.date(2026, 1, 1)
step = datetime.timedelta(days=1)
list(numeric_range(start, start + datetime.timedelta(days=3), step))
# [datetime.date(2026, 1, 1), datetime.date(2026, 1, 2), datetime.date(2026, 1, 3)]
running_min, running_max, running_mean, and running_median yield the statistic of the items seen so far. Pass maxlen to keep a sliding window. running_statistics yields a frozen Stats value with size, minimum, median, maximum, and mean.
from iterquarry import running_mean
list(running_mean([1, 2, 3, 4]))
# [1.0, 1.5, 2.0, 2.5]
difference yields the first value, then the gap from each item to the one before it. Pass func to use a function other than subtraction, or initial to set the starting previous value.
from iterquarry import difference
list(difference([10, 20, 30, 45]))
# [10, 10, 10, 15]
is_prime, factor, sieve, nth_prime, and totient cover common integer-number tasks. nth_prime is zero-based, so nth_prime(0) is 2 and nth_prime(10) is 31. multinomial computes a multinomial coefficient. convolve combines a signal with a kernel. dft and idft are the discrete Fourier transform and its inverse.
from iterquarry import convolve, factor, is_prime, sieve, totient
is_prime(97)
# True
list(factor(84))
# [2, 2, 3, 7]
list(sieve(20))
# [2, 3, 5, 7, 11, 13, 17, 19]
totient(10)
# 4
list(convolve([1, -1, -20], [1, -3]))
# [1, -4, -17, 60]
sum_of_squares, polynomial_from_roots, polynomial_derivative, and polynomial_eval are small polynomial helpers. reshape rearranges a nested iterable into a new shape.
Wrappers
always_iterable returns an iterator for any object. Strings and bytes are treated as single values. None becomes an empty iterator.
from iterquarry import always_iterable
list(always_iterable("hi"))
# ['hi']
list(always_iterable(None))
# []
always_reversible caches a one-shot iterable so it can be reversed. countable counts the items that have been consumed in items_seen. sized_iterator reports a length that shrinks as you iterate. consumer primes a generator that receives values with send. with_iter iterates a context manager and closes it when iteration finishes. callback_iter runs a callback in a background thread and exposes the calls through an iterator. AbortThread is the exception used to stop that worker. SequenceView is a read-only view of a sequence.
Other tools
side_effect calls a function for each item, or for each chunk, and still yields the original items. Optional before and after hooks run around the iteration. iterate yields start, func(start), func(func(start)), and so on. time_limited stops a slow iterable after a number of seconds and records whether it timed out. make_decorator turns an iterable tool into a decorator. map_if applies one function when a predicate is true and another when it is false. doublestarmap calls a function with each mapping unpacked as keyword arguments. raise_ raises an exception from a point in an iterator pipeline where a raise statement is awkward. consume advances an iterator and discards the items. tabulate, repeatfunc, iter_except, iter_index, and loops cover function tables, repeated calls, and exception-driven iteration.
seekable and peekable are the right tools when a pipeline needs to look backward or forward. concurrent_tee copies an iterable out to several threads. serialize and synchronized take a lock around iteration or around a function.
API reference
| Area | Names |
|---|---|
| Grouping | chunked, chunked_even, batched, ichunked, sliced, constrained_batches, distribute, divide, split_at, split_before, split_after, split_into, split_when, bucket, unzip, grouper, groupby_transform |
| Windows | pairwise, windowed, sliding_window, triplewise, stagger, windowed_complete, substrings, substrings_indexes, subslices, adjacent |
| Lookahead | spy, peekable, seekable |
| Combining | flatten, collapse, interleave, interleave_longest, interleave_evenly, interleave_randomly, roundrobin, zip_offset, zip_broadcast, sort_together, prepend, partial_product, value_chain, dotproduct, transpose, matmul |
| Selecting | first, last, one, only, nth, nth_or_last, take, tail, strip, lstrip, rstrip, unique_everseen, unique_justseen, unique, duplicates_everseen, duplicates_justseen, unique_in_window, locate, rlocate, replace, filter_except, map_except, filter_map, iter_suppress, before_and_after, takewhile_inclusive, strictly_n, partition, extract, longest_common_prefix, classify_unique |
| Summaries | ilen, sample, consecutive_groups, run_length, map_reduce, join_mappings, all_equal, all_unique, quantify, minmax, argmin, argmax, is_sorted, exactly_n, iequals, unique_to_each, first_true |
| Augmenting | count_cycle, intersperse, padded, pad_none, padnone, mark_ends, repeat_each, repeat_last, ncycles |
| Combinatorics | powerset, powerset_of_sets, distinct_permutations, distinct_combinations, partitions, set_partitions, circular_shifts, derangements, gray_product, outer_product, nth_combination, nth_combination_with_replacement, nth_permutation, nth_product, combination_index, combination_with_replacement_index, permutation_index, product_index, random_product, random_permutation, random_combination, random_combination_with_replacement, random_derangement |
| Numbers | numeric_range, difference, running_min, running_max, running_mean, running_median, running_statistics, Stats, is_prime, factor, sieve, nth_prime, totient, multinomial, convolve, dft, idft, sum_of_squares, polynomial_from_roots, polynomial_derivative, polynomial_eval, reshape |
| Wrapping | always_iterable, always_reversible, countable, sized_iterator, consumer, with_iter, callback_iter, AbortThread, SequenceView |
| Other | side_effect, iterate, time_limited, make_decorator, map_if, doublestarmap, raise_, consume, tabulate, repeatfunc, iter_except, iter_index, loops, concurrent_tee, serialize, synchronized |
Every name in that table is exported from the top-level package:
import iterquarry
iterquarry.__version__
# '2.2.1'
Development
Clone the repository, then install it in editable mode and run the test suite:
pip install -e ".[test]"
pytest
Build source and wheel archives with:
python -m build
The archives are written to dist/.
Metadata
Release files for iterquarry 2.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| iterquarry-2.2.1.tar.gz | 80.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| iterquarry-2.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 149.3 kB
Release files / iterquarry-2.2.1.tar.gz
| Download URL | iterquarry-2.2.1.tar.gz |
|---|---|
| Size | 80.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71071dffcaba4b8ca686f2a7a26b415c04f96c07da5ef2c7a62b7e75e946fc43
|
|
BLAKE2b-256 checksum How to use checksums |
1c00d680b4083c5fc23b9981cd9652310117c4b9f0cdf6e0c84061f761a2b92f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|
Release files / iterquarry-2.2.1-py3-none-any.whl
| Download URL | iterquarry-2.2.1-py3-none-any.whl |
|---|---|
| Size | 69.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3780f4d1754872aa9d1ad1ab04215cc5f4d589c3a190075dba5b8ef93e1bfdb
|
|
BLAKE2b-256 checksum How to use checksums |
3f87020eef99af8d5288ad242db5c72932d14ac426f460d1ffce4ee026d9d246
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|
Security for iterquarry 2.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.