__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)
- ChemicalException
- builtins.Exception
- builtins.BaseException
- builtins.object
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)
- NothingToPeek
- ChemicalException
- builtins.Exception
- builtins.BaseException
- builtins.object
Class variables
class Ordering
An enumeration.
class Ordering(Enum): Equal = auto() Less = auto() Greater = auto()
Ancestors (in MRO)
- Ordering
- enum.Enum
- builtins.object
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)
- TraitException
- ChemicalException
- builtins.Exception
- builtins.BaseException
- builtins.object
Class variables
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