Skip to content
Snippets Groups Projects
Commit d8e09f2c authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

2.9.10 - add DocsFromParent, ConstruableIterator and walk

parent c258ecb6
No related branches found
Tags 2.26.1
No related merge requests found
......@@ -2,4 +2,5 @@
* semantics of `catch_exception` changed
* added `DocsFromParent`
* added `ConstruableIterator`
* added `walk`
......@@ -151,6 +151,11 @@ Multirun
Generators
==========
.. autoclass:: satella.coding.sequences.ConstruableIterator
:members:
.. autofunction:: satella.coding.sequences.walk
.. autofunction:: satella.coding.chain
.. autofunction:: satella.coding.exhaust
......
......@@ -2,6 +2,7 @@ import inspect
from .recast_exceptions import silence_excs
from .decorators import wraps
from .sequences.iterators import walk
"""
Taken from http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/ and slightly
......@@ -16,7 +17,8 @@ __all__ = ['metaclass_maker', 'wrap_with', 'dont_wrap', 'wrap_property',
class DocsFromParent(type):
"""
A metaclass that fetches missing docstring's for methods from class's immediate parent
A metaclass that fetches missing docstring's for methods from the classes' bases,
looked up BFS.
>>> class Father:
>>> def test(self):
......@@ -28,11 +30,21 @@ class DocsFromParent(type):
>>> assert Child.test.__doc__ == 'my docstring'
"""
def __call__(cls, name, bases, dictionary):
def extract_bases(obj):
if isinstance(obj, tuple):
return obj
else:
return [v_base for v_base in obj.__bases__ if v_base is not object]
for key, value in dictionary.items():
if bases and callable(value) and not value.__doc__:
with silence_excs(AttributeError):
value.__doc__ = getattr(bases[0], key).__doc__
dictionary[key] = value
if callable(value) and not value.__doc__:
for base in walk(bases, extract_bases, deep_first=False):
if hasattr(base, key):
if getattr(base, key).__doc__:
value.__doc__ = getattr(base, key).__doc__
dictionary[key] = value
break
return super().__call__(name, bases, dictionary)
......
from .choose import choose, choose_one
from .iterators import infinite_counter, take_n, is_instance, skip_first, zip_shifted, stop_after, \
iter_dict_of_list, shift, other_sequence_no_longer_than, count, even, odd, n_th, enumerate, \
unique
iter_dict_of_list, shift, other_sequence_no_longer_than, count, even, odd, n_th, \
satella_enumerate as enumerate, \
unique, ConstruableIterator, walk
from .sequences import is_last, add_next, half_cartesian, group_quantity, Multirun
__all__ = ['choose', 'choose_one', 'infinite_counter', 'take_n', 'is_instance', 'is_last',
'add_next',
'add_next', 'ConstruableIterator', 'walk',
'half_cartesian', 'skip_first', 'zip_shifted', 'stop_after', 'group_quantity',
'iter_dict_of_list', 'shift', 'other_sequence_no_longer_than', 'count', 'n_th',
'even', 'odd', 'Multirun', 'enumerate', 'unique']
import itertools
import collections
import typing as tp
import warnings
from ..recast_exceptions import rethrow_as, silence_excs
from ..decorators import for_argument
T, U = tp.TypeVar('T'), tp.TypeVar('U')
IteratorOrIterable = tp.Union[tp.Iterator[T], tp.Iterable[T]]
def walk(obj: T, child_getter: tp.Callable[[T], tp.List[T]] = list,
deep_first: bool = True,
leafs_only: bool = False) -> tp.Iterator[T]:
"""
Return every node of a nested structure.
:param obj: structure to traverse. This will not appear in generator
:param child_getter: a callable to return a list of children of T.
Should return an empty list or None of there are no more children.
:param deep_first: if True, deep first will be returned, else it will be breadth first
:param leafs_only: if True, only leaf nodes (having no children) will be returned
"""
a = ConstruableIterator(child_getter(obj))
for o in a:
item_present = True
children = child_getter(o)
if children is not None:
try:
child_len = len(children)
except TypeError:
child_len = 0
if child_len:
if deep_first:
a.add_many_immediate(children)
else:
a.add_many(children)
if leafs_only:
item_present = False
if item_present:
yield o
class ConstruableIterator:
"""
An iterator that you can attach arbitrary things at the end and consume them during iteration.
Eg:
>>> a = ConstruableIterator([1, 2, 3])
>>> for b in a:
>>> if b % 2 == 0:
>>> a.add(6)
All arguments you provide to the constructor will be passed to underlying deque
"""
__slots__ = ('entries', )
def __init__(self, *args, **kwargs):
self.entries = collections.deque(*args, **kwargs) # type: tp.List[T]
def add_immediate(self, t: T) -> None:
"""
Schedule given value to be iterated over during the next __next__ call
:param t: value to iterate over
"""
self.entries.appendleft(t)
def add_many_immediate(self, t: tp.Iterable[T]) -> None:
"""
Schedule given values to be iterated over during the next __next__ call
:param t: values to iterate over
"""
for i, entry in enumerate(t):
self.entries.insert(i, entry)
def add(self, t: T) -> None:
"""
Schedule given value to be iterated over after current items
:param t: value to iterate over
"""
self.entries.append(t)
def add_many(self, t: tp.Iterable[T]) -> None:
"""
Schedule given values to be iterated over after current items
:param t: iterable of values
"""
self.entries.extend(t)
def __iter__(self) -> 'ConstruableIterator':
return self
@rethrow_as(IndexError, StopIteration)
def __next__(self) -> T:
return self.entries.popleft()
def __length_hint__(self) -> int:
return len(self.entries)
def unique(lst: IteratorOrIterable) -> tp.Iterator[T]:
"""
Return each element from lst, but return every element only once.
......@@ -239,7 +335,7 @@ def n_th(iterator: IteratorOrIterable, n: int = 0) -> T:
raise IndexError('Iterable was too short')
def enumerate(iterator: IteratorOrIterable, start: int = 0) -> tp.Iterator[tp.Tuple]:
def satella_enumerate(iterator: IteratorOrIterable, start: int = 0) -> tp.Iterator[tp.Tuple]:
"""
An enumerate that talks pretty with lists of tuples. Consider
......@@ -265,6 +361,8 @@ def enumerate(iterator: IteratorOrIterable, start: int = 0) -> tp.Iterator[tp.Tu
yield (i,) + tuple(row)
i += 1
satella_enumerate.__name__ = 'enumerate'
def take_n(iterator: IteratorOrIterable, n: int, skip: int = 0) -> tp.List[T]:
"""
......
......@@ -2,11 +2,25 @@ import sys
import unittest
from satella.coding import SelfClosingGenerator, hint_with_length, chain
from satella.coding.sequences import enumerate
from satella.coding.sequences import enumerate, ConstruableIterator, walk
class TestIterators(unittest.TestCase):
def test_walk(self):
a = [[1, 2, 3], 4, 5, 6, [7, 8, 9]]
b = walk(a, lambda x: x if isinstance(x, list) else None, leafs_only=True)
self.assertEqual(list(b), [1, 2, 3, 4, 5, 6, 7, 8, 9])
def test_construable_iterator(self):
a = ConstruableIterator([1, 2, 3])
c = []
for b in a:
if b == 2:
a.add(5)
c.append(b)
self.assertEqual(c, [1, 2, 3, 5])
def test_chain(self):
a = chain(1, 2, [3, 4, 5], 6, (i for i in range(2)))
a = list(a)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment