Documentation Status

Iterchain: Iterator chaining for Python

Iterchain is a library intended to make manipulating iterators in Python easier and more ergonomic. The design is heavily inspired by the Rust iterator design, and a lot of the functionality comes from the standard Python itertools library.

Installation

Make sure that you have pip installed and run:

pip install iterchain

For the development version:

git clone https://github.com/Evelyn-H/iterchain.git
pip install -e iterchain

Why would I need this?

Say we want to know the sum of all the squares of even numbers up to 100.
How can we do this?

Let’s try some straightforward, procedural Python:

>>> total = 0
>>> for i in range(100):
...     if i % 2 is 0:
...         total += i ** 2
...
>>> total
161700

This works, but if you read this for the first time it can take a bit of effort to figure out what’s happening, especially in slightly less trivial cases. So, how about we use iterators instead?

Well, let’s see:

>>> sum(i**2 for i in range(100) if i % 2 is 0)
161700

That’s pretty nice! Much shorter, and much easier to understand.
But there’s a problem, this pattern only works for relatively simple manipulations. In those cases you could try using the python map and filter builtins (and the slightly more hidden functools.reduce). They let you construct more complex processing chains.

Let’s rewrite our iterator to use those functions instead:

>>> sum(map(lambda x: x**2, filter(lambda x: x % 2 is 0, range(100))))
161700

Okay, now that is a mess…
I don’t know about you, but it would take me quite a while to unravel what’s happening here. The problem is that the whole expression is inside out. The filter gets applied first, but it’s hidden in the middle of the expression, and the sum gets applied last but it is all the way in the front. Makes no sense…

So, how can we improve on this? iterchain of course!
(you probably saw this coming already)

So, let’s see how it looks using iterchain:

>>> import iterchain
>>> (iterchain.count(stop=100)
...     .filter(lambda x: x % 2 is 0)
...     .map(lambda x: x**2)
...     .sum())
161700

Isn’t this much better? The operations are listed in the order that they’re executed, are clearly separated, and you can have as few or as many operations as you want. This is why you should use iterchain!

Iterator manipulation

The heart of this library <3.

Generators

iterchain also provides handy methods that let you build new Iterator instances from scratch. These are contained in the iterchain.generators sub-module, but they’re also accessible directly from the iterchain module, which is the preferred way of using them.

For example:

>>> import iterchain
>>> iterchain.count().take(4).map(lambda x: x**2).to_list()
[0, 1, 4, 9]

API

The iterchain module is callable, this means we can do this:

>>> import iterchain
>>> iterchain([1, 2, 3]).map(lambda x: x**2)

instead of:

>>> import iterchain
>>> iterchain.Iterator([1, 2, 3]).map(lambda x: x**2)

Overview

Generators
  • generators.count()
  • generators.repeat()
Chainable operations
Reduction operators
Consumers / access operators

Generators

Iterators

iterchain.chainable(func=None, *, returns_iterable=True)[source]

Decorator that allows you to add your own custom chainable methods.

The wrapped function should take the an Iterator instance as the first argument, and should return an iterable object (does not have to be an Iterator instance).

The original function is not modified and can still be used as normal.

Parameters:returns_iterable (bool) – whether or not the wrapped function returns an iterable

Example:

>>> @iterchain.chainable
>>> def plus(iterable, amount):
...     return iterable.map(lambda x: x + amount)
...
>>> iterchain([1, 2, 3]).plus(1).to_list()
[2, 3, 4]
class iterchain.Iterator(iterable)[source]

Wrapper class around python iterators

After wrapping any iterable in this class it will have access to all the methods listed below. These methods also return an Iterator instance to make them chainable.

<3

chain(*iterables) → iterchain.core.Iterator[source]
collect(constructor=None)[source]
combinations(r) → iterchain.core.Iterator[source]
combinations_with_replacement(r) → iterchain.core.Iterator[source]
compress(selectors) → iterchain.core.Iterator[source]
classmethod count(start=0, step=1) → iterchain.core.Iterator[source]
cycle() → iterchain.core.Iterator[source]
drop(n) → iterchain.core.Iterator[source]
drop_while(predicate) → iterchain.core.Iterator[source]
enumerate(start=0) → iterchain.core.Iterator[source]
filter(function) → iterchain.core.Iterator[source]
filter_false(predicate=None) → iterchain.core.Iterator[source]
flat_map(function) → iterchain.core.Iterator[source]
flatten() → iterchain.core.Iterator[source]
inspect(function) → iterchain.core.Iterator[source]
map(function) → iterchain.core.Iterator[source]

Applies a given function to all elements.

Parameters:function – the function to be called on each element
next()[source]
permutations(r=None) → iterchain.core.Iterator[source]
product(*iterables, repeat=1) → iterchain.core.Iterator[source]
classmethod range(*args) → iterchain.core.Iterator[source]

Makes a new iterator that returns evenly spaced values. (similar to the range builtin)

reduce(initial, function)[source]
classmethod repeat(item, times=None) → iterchain.core.Iterator[source]
skip(n) → iterchain.core.Iterator[source]
skip_while(predicate) → iterchain.core.Iterator[source]
slice(*args) → iterchain.core.Iterator[source]
star_map(function) → iterchain.core.Iterator[source]
take(n) → iterchain.core.Iterator[source]
take_while(predicate) → iterchain.core.Iterator[source]
to_list() → list[source]

Converts the Iterchain to a list

Returns:new list containing all the elements in this Iterchain


Indices and tables