Source code for iterchain
"""
The :code:`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**
- :meth:`generators.count`
- :meth:`generators.repeat`
- ...
**Chainable operations**
- :meth:`Iterator.map`
- :meth:`Iterator.flat_map`
- :meth:`Iterator.filter`
- ...
**Reduction operators**
- :meth:`Iterator.reduce`
- :meth:`Iterator.all`
- :meth:`Iterator.sum`
- ...
**Consumers / access operators**
- :meth:`Iterator.to_list`
- :meth:`Iterator.first`
- :meth:`Iterator.last`
- ...
"""
import sys
import types
import functools
# # # to implement:
# # generators
# count / range
# repeat
# successors (rust)
# cycle
# zip / unzip
# chain
# next
# # chainable
# map
# flat_map
# flatten
# filter
# take
# skip
# enumerate
# reversed
# sorted
# step_by
# inspect
# # consuming / non-chainable
# reduce
# all
# any
# min, max
# sum
# product
# first
# last
# nth
# for_each
# to_list / collect
# partition
# find / position'
[docs]class Iterator:
"""
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``
"""
def __init__(self, iterable):
try:
self.__iterator = iter(iterable)
except TypeError:
raise ValueError("You must pass a valid iterable object")
def __iter__(self):
return self.__iterator
def __next__(self):
return next(self.__iterator)
[docs]def chainable(func=None, *, returns_iterable=True):
"""
Decorator that allows you to add your own custom chainable methods.
The wrapped function should take the an :class:`Iterator` instance as the first argument,
and should return an iterable object (does not have to be an :class:`Iterator` instance).
The original function is not modified and can still be used as normal.
Args:
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]
"""
def _chainable(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
if returns_iterable:
return Iterator(func(self, *args, **kwargs))
else:
return func(self, *args, **kwargs)
setattr(Iterator, func.__name__, wrapper)
return func
# to allow the decorator to be used with and without arguments
if func is None:
return _chainable
else:
return _chainable(func)
# pylint: disable=too-few-public-methods
class _IterchainModule(types.ModuleType):
"""
Black magic to make the ``iterchain`` module callable.
"""
def __init__(self):
super().__init__(__name__)
self.__dict__.update(sys.modules[__name__].__dict__)
def __call__(self, iterable):
return Iterator(iterable)
# import at the end to avoid cyclic imports
from .generators import *
from .methods import *
# and change this module to our patched version
sys.modules[__name__] = _IterchainModule()