Source code for utilitools.utilities.islice

import collections
import itertools
import operator
import sys


[docs]def islice(iterable, *args): """ | Make an iterator that returns selected elements from an iterable. | An extension of |itertools.islice| that supports negative values for `start`, `stop`, and `step`. | In some cases, negative values require caching; this function minimizes the amount of memory needed. .. caution:: | Infinite caching can lead to memory issues. :param iterable: | Iterable to which the slice object will be applied. :type iterable: collections.Iterable :param args: | Arguments to create a slice object: - `stop` - `start`, `stop` - `start`, `stop`, `step` :type args: int | None :return: An iterator with the selected elements. :rtype: :obj:`utilitools.islice` :raises TypeError: - If `iterable` is not iterable. - If `args` is empty or contains more than three arguments. - If `args` values are not comparable (except `NoneType`). :raises ValueError: - If `args` values are not `NoneType` or `int`. - If `step` (from `args`) is equal to zero. """ iterator = iter(iterable) key = slice(*args) step = 1 if key.step is None else key.step length = operator.length_hint(iterable, -1) if step >= 0 and length >= 0: start = 0 if key.start is None else key.start stop = length if key.stop is None else key.stop start = length + start if start < 0 else start stop = length + stop if stop < 0 else stop yield from itertools.islice(iterable, start, stop, step) elif step < 0 and length >= 0 and hasattr(iterable, '__reversed__'): start = -1 if key.start is None else key.start stop = -length if key.stop is None else key.stop start = length - (length + start) - 1 if start < 0 else length - start - 1 stop = length - (length + stop) - 1 if stop < 0 else length - stop - 1 yield from itertools.islice(reversed(iterable), start, stop, -step) elif step >= 0: start = 0 if key.start is None else key.start stop = sys.maxsize if key.stop is None else key.stop if start >= 0 and stop >= 0: yield from itertools.islice(iterable, start, stop, step) elif start >= 0 and stop < 0: cache = collections.deque(itertools.islice(iterator, start, None)) stop = len(cache) + stop if stop > 0: yield from itertools.islice(cache, 0, stop, step) elif start < 0 and stop >= 0: cache = collections.deque(enumerate(iterator, 1), -start) iterable = map(operator.itemgetter(1), cache) length = cache[-1][0] if cache else 0 stop = min(length, stop) - max(0, length + start) if stop > 0: yield from itertools.islice(iterable, 0, stop, step) elif start < 0 and stop < 0 and start < stop: cache = collections.deque(iterator, -start) stop = len(cache) + stop yield from itertools.islice(cache, 0, stop, step) elif step < 0: start = sys.maxsize if key.start is None else key.start stop = -sys.maxsize if key.stop is None else key.stop if start >= 0 and stop >= 0 and start > stop: start, stop = min(sys.maxsize, stop + 1), min(sys.maxsize, start + 1) cache = collections.deque(itertools.islice(iterator, start, stop)) yield from itertools.islice(reversed(cache), 0, None, -step) elif start >= 0 and stop < 0: cache = collections.deque(enumerate(iterator, 1), -stop - 1) iterable = map(operator.itemgetter(1), reversed(cache)) length = cache[-1][0] if cache else 0 start = max(0, length - start - 1) yield from itertools.islice(iterable, start, None, -step) elif start < 0 and stop >= 0: cache = collections.deque(enumerate(iterator, 1)) iterable = map(operator.itemgetter(1), reversed(cache)) length = cache[-1][0] if cache else 0 start = -start - 1 stop = length - stop - 1 if stop - start >= 0: yield from itertools.islice(iterable, start, stop, -step) elif start < 0 and stop < 0 and start > stop: cache = collections.deque(iterator, -stop - 1) start = -start - 1 yield from itertools.islice(reversed(cache), start, None, -step)