Skip to main content

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.1.4 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.1.4'

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.1.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for iterquarry 2.1.4
File Size Uploaded Provenance
iterquarry-2.1.4.tar.gz 49.8 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for iterquarry 2.1.4
File Interpreter ABI Platform
iterquarry-2.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 92.0 kB

Release files / iterquarry-2.1.4.tar.gz

Download URL iterquarry-2.1.4.tar.gz
Size 49.8 kB
Tags Source
SHA-256 checksum
How to use checksums
59115e7e14e0334de8723de5dda0b09d5535c1df51a2852a6affc4806ed982fb
BLAKE2b-256 checksum
How to use checksums
4b3c1c675a9cd6bde26c192381fe741a2a19c87873e797864a548b5642b9d614
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.1.4-py3-none-any.whl

Download URL iterquarry-2.1.4-py3-none-any.whl
Size 42.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3e894856a318027e0d7c91a4705bd9ff0410e4226ca5e28eaff6646fbb2c0e05
BLAKE2b-256 checksum
How to use checksums
9e29e5791af7eb017e7ebf4952178f02600f5c10cc3f8ee7b58b34db88112936
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.1.4

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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page