Top

__init__ module

import sys, math
from enum import Enum, auto


class ChemicalException(Exception):
    "Base class for all Chemical Exceptions"


class TraitException(ChemicalException):
    "Any error having to do with traits"


class NothingToPeek(ChemicalException):
    "Raised when a call to .peek() cannot yield another element"


class it:
    """
    You can extend `it` with methods that produce iterators and methods that
    produce a value. Decorate a class or a function with `trait` respectively to
    get this to work.
    """
    traits = {}

    def __init__(self, items=[], reverse_seed=None, bounds=[]):
        self._modified = False
        self.items = iter(items)

        if isinstance(items, it):
            self._lower_bound, self._upper_bound = bounds or items.size_hint()
            self.reverse = reverse_seed or items.reverse

        else:
            if bounds:
                self._lower_bound, self._upper_bound = bounds
            else:
                try:
                    self._lower_bound = len(items)
                    self._upper_bound = len(items)
                except TypeError:
                    self._lower_bound = 0
                    self._upper_bound = None
            try:
                self.reverse = reverse_seed or it(reversed(items))
            except TypeError:
                self.reverse = None

    def __copy__(self):
        from copy import copy
        return it(copy(self.items), copy(self.reverse))

    def __str__(self):
        return f'<{self.__class__.__name__} object at {hex(id(self))}>'

    def __iter__(self):
        return self

    def __reversed__(self):
        if self._modified:
            raise ChemicalException(
                'Cannot reverse an iterator that has already yielded at least '
                'one item with next()'
            )

        if not self.reverse:
            raise ChemicalException('Underlying collection cannot be reversed.')

        else:
            return self.__get_reversed__()

    def __get_reversed__(self):
        return it(self.reverse, self.items, self.size_hint())

    def __next__(self):
        self._modified = True
        return self.__get_next__()

    def __get_next__(self):
        return next(self.items)

    def __dir__(self):
        from itertools import chain
        keys = set(self.__dict__.keys()) ^ {'items'}
        return sorted(set(chain(keys, self.traits.keys())))

    def __getattr__(self, name):
        if name not in it.traits:
            raise TraitException(
                f'Trait or extension method "{name}" not found for {self}.'
            )

        clazz = it.traits[name]

        class wrap:
            __doc__ = clazz.__doc__

            def __init__(self, items, clazz, name):
                self.items = items
                self.clazz = clazz
                self.name = name

            def __repr__(self):
                hid = hex(id(self.clazz))
                return f'<{self.__module__}.it.{self.name} at {hid}>'

            def __call__(self, *args, **kwargs):
                return self.clazz(self.items, *args, **kwargs)

        return wrap(self, clazz, name)

    def next(self):
        return next(self)

    def rev(self):
        return reversed(self)

    def size_hint(self):
        return self._lower_bound, self._upper_bound


def trait(bind=None):
    def inner(*args, **kwargs):
        nonlocal bind
        return bind(*args, **kwargs)

    def wrapper(clazz):
        __doc__ = clazz.__doc__
        nonlocal bind
        it.traits[bind] = clazz
        return clazz

    if isinstance(bind, str):
        return wrapper

    it.traits[bind.__name__.lower()] = bind
    inner.__doc__ = bind.__doc__
    return inner


class Ordering(Enum):
    Equal = auto()
    Less = auto()
    Greater = auto()


class Ref:
    def __init__(self, val):
        self.val = val

    def __call__(self, val):
        self.val = val
        return val

    def __getattr__(self, name):
        if name != '_':
            raise ChemicalException("To get a reference's value, use: `ref._`")
        return self.val

    def set(self, val):
        self.val = val

    def get(self):
        return self.val


# Allow all iterators and aggregators to be available upon import
from . aggregators import *
from . iterators import *

Functions

def Inspect(

*args, **kwargs)

Allows a function to be applied to each element in an iterator without modifying it in any way.

This is useful for inspecting the results of an iterator.

Examples

# Prints each element on it's own line
it('abc').inspect(print).go()

(it('abc')
    .inspect(lambda x: print('Before:', x))
    .map(lambda x: x.upper())
    .inspect(lambda x: print('After:', x))
    .go()
)
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def Peekable(

*args, **kwargs)

Adds a method to any iterator that allows the next element to be revealed without consuming it.

Examples

itr = it('cba').rev().peekable()
assert itr.peek() == 'a'
assert itr.next() == 'a'
assert itr.peek() == 'b'
assert itr.next() == 'b'
assert itr.peek() == 'c'
assert itr.next() == 'c'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def Skip(

*args, **kwargs)

Lazily skip a number of items in the iterator chain.

Examples

assert it('asdf').skip(1).collect(str) == 'sdf'
assert it('asdf').rev().skip(1).rev().collect(str) == 'asd'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def cmp(

*args, **kwargs)

Lexicographically compares elements of an iterator with those of another.

Essentially, this compares the length of one iterator with another.

Examples

assert it('asdf').cmp((1, 2, 3, 4)) == Ordering.Equal
assert it('asdf').cmp((1, 2, 3, 4, 5)) == Ordering.Less
assert it('asdf').cmp((1, 2, 3)) == Ordering.Greater
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def cmp_by(

*args, **kwargs)

Checks if this iterator is equal with another using a comparison function.

Examples

func = lambda a, b: a.upper() == b.upper()
assert it('asdf').cmp_by('asdf', func)
assert not it('bsdf').cmp_by('asdf', func)
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def collect(

*args, **kwargs)

Consumes the iterator and returns a collection of the given type.

Special handling is given to the str type. __str__() is called on each element and then the resulting list of strings are concatenated together to form one string.

All other collections are formed by passing the iterator to the constructor.

Examples

assert it(range(3)).collect() == [0, 1, 2]
assert it(range(3)).collect(tuple) == (0, 1, 2)
assert it(range(3)).collect(set) == {0, 1, 2}
assert it('abc').collect(str) == 'abc'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def count(

*args, **kwargs)

Returns the length of the iterator, consuming it.

Unfortunately, a __len__() method cannot be added to it because loops such as for will call it if it exists, which will consume the iterator before it even runs the loop.

However, it is better than no __len__() method exists because the length of an iterator is seldom known. See also size_hint().

Examples

assert it('abc').count() == 3
assert it('abc').skip(1).count() == 2
assert it('abc').skip(1).take(2).count() == 2
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def eq(

*args, **kwargs)

Returns if the elements of this iterator are equal to another, consuming them both.

Examples

assert it('asdf').eq('asdf')
assert not it('asdf').eq('asdfasdfasdfasdf')
assert it('asdf').skip(1).eq('sdf')
assert not it('asdf').eq((2, 1, 23))
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def filter(

*args, **kwargs)

Filters out elements of the iterator based on the provided lambda.

Examples

assert it(range(5)).filter(lambda x: not x % 2).collect() == [0, 2, 4]
assert it('abcd').filter(lambda x: x in 'bd').collect(str) == 'bd'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def find(

*args, **kwargs)

Uses a function to search for an item if it exists and returns the item.

The function should return True if the item is found.

Examples

assert it('asdf').find(lambda x: x.upper() == 'S') == 's'
assert it('asdf').rev().find(lambda x: x.upper() == 'S') == 's'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def flatten(

*args, **kwargs)

Removes one level of grouping from each element in an iterator.

Can be used to flatten a list of lists. However, it will not flatten a list containing lists of lists.

Examples

assert it([[1], [2], [3]]).flatten().collect() == [1, 2, 3]
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def fold(

*args, **kwargs)

An iterator method that applies a function, producing a single, final value.

fold() takes two arguments: an initial value, and a closure with two arguments: an 'accumulator', and an element. The closure returns the value that the accumulator should have for the next iteration.

The initial value is the value the accumulator will have on the first call.

After applying this closure to every element of the iterator, fold() returns the accumulator.

This operation is sometimes called 'reduce' or 'inject'.

Folding is useful whenever you have a collection of something, and want to produce a single value from it.

In order to achieve this in Python, a special type had to be created in order to retain a reference to a value over the lifetime of the loop.

The Ref type is used to allow you to assign within a lambda function.

To get the value contained with a reference (dereference), simply access the _ attribute. To assign to the same value within an expression, you can simply call the reference. A common pattern with the Ref type is to assign and dereference in the same expression like this: my_ref(my_ref._ + 1)

Examples

assert it((1, 2, 3)).fold(1, lambda a, i: a(a._ * i)) == 6

# These are equivalent:
my_ref = Ref(2)

print(my_ref.val)
print(my_ref.get())
print(my_ref._)

my_ref.val = 123
my_ref.set(123)
my_ref(123)  # Assignment within an expression
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def for_each(

*args, **kwargs)

Iterator version of a for loop.

Executes a function on each element without modifying the element.

Examples

# Prints each element on its own line.
assert it('asdf').for_each(print)
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def ge(

*args, **kwargs)

Returns if an iterator is lexicographically longer/equal to another.

Examples

assert it('asdf').ge('asd')
assert it('asdf').cycle().take(5).ge('asd')
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def go(

*args, **kwargs)

Consumes the iterator without returning anything.

Useful for situations in which you want to avoid a large numbers of allocations. For instance, without go(), you would have to consume the iterator using a for loop, or calling list(it). Either option is less convenient than just using go().

Examples

seen = []
it('abc').inspect(lambda x: seen.append(x.upper())).go()
assert seen == ['A', 'B', 'C']
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def gt(

*args, **kwargs)

Returns if an iterator is lexicographically longer than another.

Examples

assert it('asdf').gt('asd')
assert it('asdf').cycle().take(5).gt('asd')
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def is_sorted(

*args, **kwargs)

Returns True if the iterator is sorted, consuming it.

Examples

assert it((1, 2, 3)).is_sorted()
assert not it((2, 3, 1)).is_sorted()
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def last(

*args, **kwargs)

Returns the last element of an iterator, consuming it.

Examples

assert it('abc').last() == 'c'
assert it('abc').skip(1).last() == 'c'
assert it('abc').cycle().take(8).last() == 'b'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def le(

*args, **kwargs)

Returns if an iterator is lexicographically shorter/equal to another.

Examples

assert it('as').le('asd')
assert it('asdf').cycle().take(5).le('asdfa')
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def lt(

*args, **kwargs)

Returns if an iterator is lexicographically shorter than another.

Examples

assert it('asdf').lt('asddfas')
assert it('asdf').cycle().take(5).lt('asdfas')
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def max_by_key(

*args, **kwargs)

Uses a function to determine the largest item in an iterator.

Examples

assert it([1]).max_by_key(lambda x: -x) == 1
assert it((1, 2, 3, 4)).max_by_key(lambda x: -x) == 1
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def min_by_key(

*args, **kwargs)

Uses a function to determine the smallest item in an iterator.

Examples

assert it([1]).min_by_key(lambda x: -x) == 1
assert it((1, 2, 3, 4)).min_by_key(lambda x: -x) == 4
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def neq(

*args, **kwargs)

Returns if the elements of this iterator are not equal to another, consuming them both.

Examples

assert it('asdf').neq('asdf1')
assert it('asdf').skip(1).neq('asdf')
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def nth(

*args, **kwargs)

Returns the nth element from an iterator.

If 3 is passed, this returns the 3rd element, and so on.

If a negative number is passed, this is the equivalent of the following:

itr = it('abc')
assert itr.nth(-1) == itr.rev().nth(1)

Examples

assert it('abc').nth(3) == 'c'
assert it('abc').skip(1).nth(2) == 'c'
assert it('abc').cycle().nth(23) == 'b'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def par_iter(

*args, **kwargs)

Iterate through the elements of an iterator concurrently.

Since CPython is only able to execute 1 true thread at a time, only the illusion of parallelism is achievable, which can definitely be highly useful in situations where tasks need to not execute sequencially.

Please see this talk on why concurrency is not the same as parallelism.

It should be noted that the name "par_iter" was taken from Rayon's par_iter function.

The order of items in the underlying iterator are maintained.

If your item handling code has side-effects, par_iter may not be the best solution for you because it handles each item concurrently and those side effects may occur in a different order.

Examples

The order of the returned elements is maintained even though they are processed concurrently.

itr = it(range(3)).par_iter()
assert itr.next() == 0
assert itr.next() == 1
assert itr.next() == 2

Making HTTP requests is faster using par_iter:

from requests import get as GET

urls = [...]

results = (it(urls)
    .map(lambda u: GET(u))
    .map(lambda u: u.text if u.ok else u.reason)
    .par_iter()
    .collect()
)
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def partition(

*args, **kwargs)

Takes a Lambda that returns a boolean and returns two lists containing the items that returned True and the items that returned False.

Examples

assert it((1, 2, 3)).partition(lambda x: x % 2 == 0) == (
    [2], [1, 3]
)
assert it('aSdF').partition(lambda x: x.upper() == x) == (
    ['S', 'F'], ['a', 'd']
)

assert it((1, 2, 3)).rev().partition(lambda x: x % 2 == 0) == (
    [2], [3, 1]
)
assert it('aSdF').rev().partition(lambda x: x.upper() == x) == (
    ['F', 'S'], ['d', 'a']
)
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def position(

*args, **kwargs)

Uses a function to obtain the index of an element within an iterator.

The function should return True if the item is found.

Examples

assert it('asdf').position(lambda x: x == 'd') == 2
assert it('asdf').rev().position(lambda x: x == 'd') == 1
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def product(

*args, **kwargs)

Multiplies all the elements together, consuming the iterator.

Examples

assert it((1, 2, 3)).product() == 6
assert it((-1, 2, 3)).product() == -6
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def scan(

*args, **kwargs)

An iterator adaptor similar to fold that holds internal state and produces a new iterator.

scan() takes two arguments: an initial value which seeds the internal state, and a closure with two arguments, the first being a mutable reference to the internal state and the second an iterator element. The closure can assign to the internal state to share state between iterations.

On iteration, the closure will be applied to each element of the iterator and the return value from the closure.

Examples

assert (it((1, 2, 3))
    .scan(1, lambda acc, ele: acc(acc._ * ele))
    .collect()
) == [1, 2, 6]
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def skip_while(

*args, **kwargs)

Returns any element after each one that doesn't match the given function.

Examples

assert (it('abDF')
    .skip_while(lambda x: x.upper() != x)
    .collect(str)
) == 'DF'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def take(

*args, **kwargs)

Returns only the number of items you specify from an iterator.

Examples

assert it(range(5)).take(2).collect() == [0, 1]
assert it(range(5)).rev().take(3).collect() == [4, 3, 2]
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def take_while(

*args, **kwargs)

Only returns elements from the iterator while a given function returns True.

Examples

assert it('ab7f').take_while(lambda x: x.isalpha()).collect(str) == 'ab'
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

def trait(

bind=None)

def trait(bind=None):
    def inner(*args, **kwargs):
        nonlocal bind
        return bind(*args, **kwargs)

    def wrapper(clazz):
        __doc__ = clazz.__doc__
        nonlocal bind
        it.traits[bind] = clazz
        return clazz

    if isinstance(bind, str):
        return wrapper

    it.traits[bind.__name__.lower()] = bind
    inner.__doc__ = bind.__doc__
    return inner

def unzip(

*args, **kwargs)

Returns two lists created from the first and second index of every collection found within the iterator.

Examples

gold = [*range(9)], [*range(8, -1, -1)]
assert it(range(9)).zip(range(8, -1, -1)).unzip() == gold
def inner(*args, **kwargs):
    nonlocal bind
    return bind(*args, **kwargs)

Classes

class ChemicalException

Base class for all Chemical Exceptions

class ChemicalException(Exception):
    "Base class for all Chemical Exceptions"

Ancestors (in MRO)

Class variables

var args

class NothingToPeek

Raised when a call to .peek() cannot yield another element

class NothingToPeek(ChemicalException):
    "Raised when a call to .peek() cannot yield another element"

Ancestors (in MRO)

Class variables

var args

Inheritance: ChemicalException.args

class Ordering

An enumeration.

class Ordering(Enum):
    Equal = auto()
    Less = auto()
    Greater = auto()

Ancestors (in MRO)

Class variables

var Equal

var Greater

var Less

var name

var value

class Ref

class Ref:
    def __init__(self, val):
        self.val = val

    def __call__(self, val):
        self.val = val
        return val

    def __getattr__(self, name):
        if name != '_':
            raise ChemicalException("To get a reference's value, use: `ref._`")
        return self.val

    def set(self, val):
        self.val = val

    def get(self):
        return self.val

Ancestors (in MRO)

  • Ref
  • builtins.object

Static methods

def __init__(

self, val)

Initialize self. See help(type(self)) for accurate signature.

def __init__(self, val):
    self.val = val

def get(

self)

def get(self):
    return self.val

def set(

self, val)

def set(self, val):
    self.val = val

Instance variables

var val

class TraitException

Any error having to do with traits

class TraitException(ChemicalException):
    "Any error having to do with traits"

Ancestors (in MRO)

Class variables

var args

Inheritance: ChemicalException.args

class it

You can extend it with methods that produce iterators and methods that produce a value. Decorate a class or a function with trait respectively to get this to work.

class it:
    """
    You can extend `it` with methods that produce iterators and methods that
    produce a value. Decorate a class or a function with `trait` respectively to
    get this to work.
    """
    traits = {}

    def __init__(self, items=[], reverse_seed=None, bounds=[]):
        self._modified = False
        self.items = iter(items)

        if isinstance(items, it):
            self._lower_bound, self._upper_bound = bounds or items.size_hint()
            self.reverse = reverse_seed or items.reverse

        else:
            if bounds:
                self._lower_bound, self._upper_bound = bounds
            else:
                try:
                    self._lower_bound = len(items)
                    self._upper_bound = len(items)
                except TypeError:
                    self._lower_bound = 0
                    self._upper_bound = None
            try:
                self.reverse = reverse_seed or it(reversed(items))
            except TypeError:
                self.reverse = None

    def __copy__(self):
        from copy import copy
        return it(copy(self.items), copy(self.reverse))

    def __str__(self):
        return f'<{self.__class__.__name__} object at {hex(id(self))}>'

    def __iter__(self):
        return self

    def __reversed__(self):
        if self._modified:
            raise ChemicalException(
                'Cannot reverse an iterator that has already yielded at least '
                'one item with next()'
            )

        if not self.reverse:
            raise ChemicalException('Underlying collection cannot be reversed.')

        else:
            return self.__get_reversed__()

    def __get_reversed__(self):
        return it(self.reverse, self.items, self.size_hint())

    def __next__(self):
        self._modified = True
        return self.__get_next__()

    def __get_next__(self):
        return next(self.items)

    def __dir__(self):
        from itertools import chain
        keys = set(self.__dict__.keys()) ^ {'items'}
        return sorted(set(chain(keys, self.traits.keys())))

    def __getattr__(self, name):
        if name not in it.traits:
            raise TraitException(
                f'Trait or extension method "{name}" not found for {self}.'
            )

        clazz = it.traits[name]

        class wrap:
            __doc__ = clazz.__doc__

            def __init__(self, items, clazz, name):
                self.items = items
                self.clazz = clazz
                self.name = name

            def __repr__(self):
                hid = hex(id(self.clazz))
                return f'<{self.__module__}.it.{self.name} at {hid}>'

            def __call__(self, *args, **kwargs):
                return self.clazz(self.items, *args, **kwargs)

        return wrap(self, clazz, name)

    def next(self):
        return next(self)

    def rev(self):
        return reversed(self)

    def size_hint(self):
        return self._lower_bound, self._upper_bound

Ancestors (in MRO)

  • it
  • builtins.object

Class variables

var traits

Static methods

def __init__(

self, items=[], reverse_seed=None, bounds=[])

Initialize self. See help(type(self)) for accurate signature.

def __init__(self, items=[], reverse_seed=None, bounds=[]):
    self._modified = False
    self.items = iter(items)
    if isinstance(items, it):
        self._lower_bound, self._upper_bound = bounds or items.size_hint()
        self.reverse = reverse_seed or items.reverse
    else:
        if bounds:
            self._lower_bound, self._upper_bound = bounds
        else:
            try:
                self._lower_bound = len(items)
                self._upper_bound = len(items)
            except TypeError:
                self._lower_bound = 0
                self._upper_bound = None
        try:
            self.reverse = reverse_seed or it(reversed(items))
        except TypeError:
            self.reverse = None

def next(

self)

def next(self):
    return next(self)

def rev(

self)

def rev(self):
    return reversed(self)

def size_hint(

self)

def size_hint(self):
    return self._lower_bound, self._upper_bound

Instance variables

var items