def breadth_first(start, expand): """Performs a breadth-first search of a graph-like structure. :param start: Node to start the search from :param expand: Function taking a node as an argument and returning iterable of its child nodes :return: Iterable of nodes in the BFS order Example:: tree = json.loads(some_data) for item in breadth_first(tree, key_func('children', default=())): do_something_with(item) """ ensure_callable(expand) def generator(): queue = deque([start]) while queue: node = queue.popleft() yield node queue.extend(expand(node)) return generator()
def method_decorator(decor): """Decorator for function decorators (sic), written either as classes or functions. In either case, the decorator ``decor`` must be "doubly-callable": * for classes, this means implementing ``__call__`` method in addition to possible ``__init__`` * for functions, this means returning a function that acts as an actual decorator, i.e. taking a function and returning its decorated version Although it works for any decorator, it's useful mainly for those that should take optional arguments. If the decorator is adorned with ``@method_decorator``, it's possible to use it without the pair of empty parentheses:: class Foo(object): @enhanced # rather than @enhanced() def foo(self): pass when we don't want to pass any arguments to it. .. note:: :func:`function_decorator` makes decorator applicable only to methods inside a class. Use :func:`decorator`, :func:`function_decorator` or :func:`class_decorator` for decorators that should be applicable to other language constructs. .. versionadded:: 0.0.3 """ ensure_callable(decor) return _wrap_decorator(decor, "methods", is_method)
def decorator(decor): """Decorator for decorators (sic), written either as classes or functions. In either case, the decorator ``decor`` must be "doubly-callable": * for classes, this means implementing ``__call__`` method in addition to possible ``__init__`` * for functions, this means returning a function that acts as an actual decorator, i.e. taking a function and returning its decorated version Although it works for any decorator, it's useful mainly for those that should take optional arguments. If the decorator is adorned with ``@decorator``, it's possible to use it without the pair of empty parentheses:: @enhanced # rather than @enhanced() def foo(): pass when we don't want to pass any arguments to it. .. note:: :func:`decorator` makes decorator appicable for both functions and classes. If you want to restrict the type of decorator targets, use :func:`function_decorator`, :func:`method_decorator` or :func:`class_decorator`. """ ensure_callable(decor) return _wrap_decorator(decor, "functions or classes", or_(inspect.isfunction, inspect.isclass))
def flip(f): """Flip the order of positonal arguments of given function.""" ensure_callable(f) result = lambda *args, **kwargs: f(*reversed(args), **kwargs) functools.update_wrapper(result, f, ('__name__', '__module__')) return result
def terminator(method): """Mark a public method of @\ :class:`fluent` class as *terminator*, i.e. a method that ends a chain of consecutive calls and returns a result. """ ensure_callable(method) method.__fluent_terminator = True return method
def terminator(method): """Mark a public method of @\ :class:`fluent` class as *terminator*, i.e. a method that ends a chain of consecutive calls and returns a result. """ ensure_callable(method) method.__fluent_terminator = True return method
def uncurry(f): """Convert a curried function into a function on tuples (of positional arguments) and dictionaries (of keyword arguments). """ ensure_callable(f) result = lambda args=(), kwargs=None: f(*args, **(kwargs or {})) functools.update_wrapper(result, f, ('__name__', '__module__')) return result
def not_(f): """Creates a function that returns a Boolean negative of provided function. :param f: Function to create a Boolean negative of :return: Function ``g`` such that ``g(<args>) == not f(<args>)`` for any ``<args>`` """ ensure_callable(f) return lambda *args, **kwargs: not f(*args, **kwargs)
def transform(self, func): """Transform the value stored in this variable by applying a specified function to it. :param func: Unary transformation function :raise ValueAbsentError: When the variable has no value :raise TypeError: When ``func`` is not a callable .. versionadded:: 0.0.2 """ self._ensure_has_value() ensure_callable(func) self.value = func(self.value)
def transform(self, func): """Transform the value stored in this variable by applying a specified function to it. :param func: Unary transformation function :raise ValueAbsentError: When the variable has no value :raise TypeError: When ``func`` is not a callable .. versionadded:: 0.0.2 """ self._ensure_has_value() ensure_callable(func) self.value = func(self.value)
def filterkeys(predicate, dict_): """Return a new dictionary comprising of keys for which ``predicate`` returns True, and their corresponding values. :param predicate: Predicate taking a dictionary key, or None """ predicate = bool if predicate is None else ensure_callable(predicate) ensure_mapping(dict_) return dict_.__class__((k, v) for k, v in iteritems(dict_) if predicate(k))
def starfilteritems(predicate, dict_): """Return a new dictionary comprising of keys and values for which ``predicate`` returns True. :param predicate: Predicate taking key and value, or None .. versionchanged:: 0.0.2 Renamed ``starfilteritems`` for consistency with :func:`starmapitems`. """ ensure_mapping(dict_) if predicate is None: predicate = lambda k, v: all((k, v)) else: ensure_callable(predicate) return dict_.__class__( (k, v) for k, v in iteritems(dict_) if predicate(k, v))
def filtervalues(predicate, dict_): """Returns a new dictionary comprising of values for which ``predicate`` return True, and keys that corresponded to them. :param predicate: Predicate taking a dictionary value, or None """ predicate = bool if predicate is None else ensure_callable(predicate) ensure_mapping(dict_) return dict_.__class__((k, v) for k, v in iteritems(dict_) if predicate(v))
def with_(contextmanager, do): """Emulate a `with`` statement, performing an operation within context. :param contextmanager: Context manager to use for ``with`` statement :param do: Operation to perform: callable that receives the ``as`` value :return: Result of the operation Example:: # read all lines from given list of ``files`` all_lines = sum((with_(open(filename), do=dotcall('readlines')) for filename in files), []) """ ensure_contextmanager(contextmanager) ensure_callable(do) with contextmanager as value: return do(value)
def with_(contextmanager, do): """Emulate a `with`` statement, performing an operation within context. :param contextmanager: Context manager to use for ``with`` statement :param do: Operation to perform: callable that receives the ``as`` value :return: Result of the operation Example:: # read all lines from given list of ``files`` all_lines = sum((with_(open(filename), do=dotcall('readlines')) for filename in files), []) """ ensure_contextmanager(contextmanager) ensure_callable(do) with contextmanager as value: return do(value)
def mapvalues(function, dict_): """Return a new dictionary where the values come from applying ``function`` to the values of given dictionary. :param function: Function taking a dictionary value, or None (corresponding to identity function) .. versionadded:: 0.0.2 """ ensure_mapping(dict_) function = identity() if function is None else ensure_callable(function) return dict_.__class__((k, function(v)) for k, v in iteritems(dict_))
def filteritems(predicate, dict_): """Return a new dictionary comprising of items for which ``predicate`` returns True. :param predicate: Predicate taking a key-value pair, or None .. versionchanged: 0.0.2 ``predicate`` is now taking a key-value pair as a single argument. """ predicate = all if predicate is None else ensure_callable(predicate) ensure_mapping(dict_) return dict_.__class__(ifilter(predicate, iteritems(dict_)))
def depth_first(start, descend): """Performs a depth-first search of a graph-like structure. :param start: Node to start the search from :param expand: Function taking a node as an argument and returning iterable of its child nodes :return: Iterable of nodes in the DFS order Example:: for node in depth_first(graph, attr_func('adjacent')): visit(node) """ ensure_callable(descend) def generator(): stack = [start] while stack: node = stack.pop() yield node stack.extend(descend(node)) return generator()
def starmapitems(function, dict_): """Return a new dictionary where the keys and values come from applying ``function`` to the keys and values of given dictionary. .. warning:: If ``function`` returns a key-value pair with the same key more than once, it is undefined which value will be chosen for that key in the resulting dictionary. :param function: Function taking key and value as two arguments and returning a new key-value pair, or None (corresponding to identity function) .. versionadded:: 0.0.2 """ ensure_mapping(dict_) if function is None: function = lambda k, v: (k, v) else: ensure_callable(function) return dict_.__class__(starmap(function, iteritems(dict_)))
def _index(*args, **kwargs): """Implementation of list searching. :param of: Element to search for :param where: Predicate to search for :param in_: List to search in :param start: Start index for the lookup :param step: Counter step (i.e. in/decrement) for each iteration :return: Pair of ``(list, index)``, where ``list`` is the list we searched in and ``index`` is the index of the first element found, or -1 """ start = kwargs.pop('start', 0) step = kwargs.pop('step', 1) if len(args) == 2: elem, list_ = args ensure_sequence(list_) predicate = lambda item: item == elem else: ensure_keyword_args(kwargs, mandatory=('in_',), optional=('of', 'where')) if 'of' in kwargs and 'where' in kwargs: raise TypeError( "either an item or predicate must be supplied, not both") if not ('of' in kwargs or 'where' in kwargs): raise TypeError("an item or predicate must be supplied") list_ = ensure_sequence(kwargs['in_']) if 'where' in kwargs: predicate = ensure_callable(kwargs['where']) else: elem = kwargs['of'] predicate = lambda item: item == elem len_ = len(list_) start = max(0, min(len_ - 1, start)) i = start while 0 <= i < len_: if predicate(list_[i]): return list_, i i += step else: return list_, -1
def mapkeys(function, dict_): """Return a new dictionary where the keys come from applying ``function`` to the keys of given dictionary. .. warning:: If ``function`` returns the same value for more than one key, it is undefined which key will be chosen for the resulting dictionary. :param function: Function taking a dictionary key, or None (corresponding to identity function) .. versionadded:: 0.0.2 """ ensure_mapping(dict_) function = identity() if function is None else ensure_callable(function) return dict_.__class__((function(k), v) for k, v in iteritems(dict_))
def mapitems(function, dict_): """Return a new dictionary where the keys and values come from applying ``function`` to key-value pairs from given dictionary. .. warning:: If ``function`` returns a key-value pair with the same key more than once, it is undefined which value will be chosen for that key in the resulting dictionary. :param function: Function taking a key-value pair as a single argument, and returning a new key-value pair; or None (corresponding to identity function) .. versionadded:: 0.0.2 """ ensure_mapping(dict_) function = identity() if function is None else ensure_callable(function) return dict_.__class__(imap(function, iteritems(dict_)))
def unique(iterable, key=None): """Removes duplicates from given iterable, using given key as criterion. :param key: Key function which returns a hashable, uniquely identifying an object. :return: Iterable with duplicates removed """ ensure_iterable(iterable) key = hash if key is None else ensure_callable(key) def generator(): seen = set() for elem in iterable: k = key(elem) if k not in seen: seen.add(k) yield elem return generator()
def test_lambda(self): func = lambda : self.fail("lambda must not be actually called") __unit__.ensure_callable(func)
def test_few_builtins(self): __unit__.ensure_callable(open) __unit__.ensure_callable(min) __unit__.ensure_callable(sum) __unit__.ensure_callable(str.__add__)
def __init__(self, getter): ensure_callable(getter) self.getter = getter
def try_(block, except_=None, else_=None, finally_=None): """Emulate a ``try`` block. :param block: Function to be executed within the ``try`` statement :param except_: Function to execute when an :class:`Exception` occurs. It receives a single argument: the exception object. Alternatively, a list of key-value pairs can be passed, mapping exception types to their handler functions. :param else_: Function to execute when ``block`` completes successfully. Note that it requires ``except_`` to be provided as well :param finally_: Function to execute at the end, regardless of whether an exception occurred or not :return: If no exception was raised while executing ``block``, the result of ``else_`` (if provided) or ``block`` is returned. Otherwise, the result of ``except_`` is returned. Note that :func:`try_` can _still_ raise an exception if it occurs while evaluating ``except_``, ``else_`` or ``finally_`` functions. """ ensure_callable(block) if not (except_ or else_ or finally_): raise TypeError("at least one of `except_`, `else_` or `finally_` " "functions must be provided") if else_ and not except_: raise TypeError("`else_` can only be provided along with `except_`") if except_: if callable(except_): except_ = [(Exception, except_)] else: ensure_iterable(except_) if is_mapping(except_): ensure_ordered_mapping(except_) except_ = except_.items() def handle_exception(): """Dispatch current exception to proper handler in ``except_``.""" exc_type, exc_object = sys.exc_info()[:2] for t, handler in except_: if issubclass(exc_type, t): return handler(exc_object) raise if else_: ensure_callable(else_) if finally_: ensure_callable(finally_) try: block() except: return handle_exception() else: return else_() finally: finally_() else: try: block() except: return handle_exception() else: return else_() else: if finally_: ensure_callable(finally_) try: return block() except: return handle_exception() finally: finally_() else: try: return block() except: return handle_exception() elif finally_: ensure_callable(finally_) try: return block() finally: finally_()
def test_some_object(self): with self.assertRaises(TypeError): __unit__.ensure_callable(object())
def topological_order(nodes, incoming): """Performs topological sort of a DAG-like structure (directed acyclic graph). :param nodes: Collection of nodes :param incoming: Function taking node as an argument and returning iterable of nodes with edges pointing _towards_ given one :return: Iterable of nodes in the topological order .. note:: ``incoming`` function works in _reverse_ to the typical adjacency relation in graphs: if ``A in incoming(B)``, it implies that ``A->B`` is among the graph edges (**not** ``B->A``!). This reversal is useful in practice when dealing with graphs representing dependencies, module imports, header includes, and so on. Example:: for package in topological_order(packages, attr_func('dependencies')): install(package) .. versionadded:: 0.0.4 """ ensure_iterable(nodes) ; ensure_countable(nodes) ensure_callable(incoming) # data structure for tracking node's visit state NOT_VISITED, VISITING, VISITED = range(3) visit_states = {} visit_state = lambda node: visit_states.get(id(node), NOT_VISITED) def visit(node): """Topological sort visitor function.""" if visit_state(node) == VISITING: raise ValueError("cycle found on node %r" % (node,)) if visit_state(node) == NOT_VISITED: visit_states[id(node)] = VISITING for neighbor in incoming(node): for n in visit(neighbor): yield n visit_states[id(node)] = VISITED yield node def generator(): """Main generator function that loops through the nodes until we've visited them all. """ visited_count = 0 while visited_count < len(nodes): visited_count = 0 for node in nodes: if visit_state(node) == VISITED: visited_count += 1 else: for n in visit(node): yield n return generator()
def test_string(self): with self.assertRaises(TypeError): __unit__.ensure_callable("foo")
def try_(block, except_=None, else_=None, finally_=None): """Emulate a ``try`` block. :param block: Function to be executed within the ``try`` statement :param except_: Function to execute when an :class:`Exception` occurs. It receives a single argument: the exception object. Alternatively, a list of key-value pairs can be passed, mapping exception types to their handler functions. :param else_: Function to execute when ``block`` completes successfully. Note that it requires ``except_`` to be provided as well :param finally_: Function to execute at the end, regardless of whether an exception occurred or not :return: If no exception was raised while executing ``block``, the result of ``else_`` (if provided) or ``block`` is returned. Otherwise, the result of ``except_`` is returned. Note that :func:`try_` can _still_ raise an exception if it occurs while evaluating ``except_``, ``else_`` or ``finally_`` functions. """ ensure_callable(block) if not (except_ or else_ or finally_): raise TypeError("at least one of `except_`, `else_` or `finally_` " "functions must be provided") if else_ and not except_: raise TypeError("`else_` can only be provided along with `except_`") if except_: if callable(except_): except_ = [(Exception, except_)] else: ensure_iterable(except_) if is_mapping(except_): ensure_ordered_mapping(except_) except_ = except_.items() def handle_exception(): """Dispatch current exception to proper handler in ``except_``.""" exc_type, exc_object = sys.exc_info()[:2] for t, handler in except_: if issubclass(exc_type, t): return handler(exc_object) raise if else_: ensure_callable(else_) if finally_: ensure_callable(finally_) try: block() except: return handle_exception() else: return else_() finally: finally_() else: try: block() except: return handle_exception() else: return else_() else: if finally_: ensure_callable(finally_) try: return block() except: return handle_exception() finally: finally_() else: try: return block() except: return handle_exception() elif finally_: ensure_callable(finally_) try: return block() finally: finally_()
def test_lambda(self): func = lambda: self.fail("lambda must not be actually called") __unit__.ensure_callable(func)
def test_few_builtins(self): __unit__.ensure_callable(open) __unit__.ensure_callable(min) __unit__.ensure_callable(sum) __unit__.ensure_callable(str.__add__)
def test_function(self): def func(): self.fail("function must not be actually called") __unit__.ensure_callable(func)
def test_class(self): class Foo(object): def __init__(self_): self.fail("class must not be actually instantiated") __unit__.ensure_callable(Foo)
def test_callable_object(self): class Foo(object): def __call__(self_): self.fail("object must not be actually called") __unit__.ensure_callable(Foo())
def test_string(self): with self.assertRaises(TypeError): __unit__.ensure_callable("foo")
def test_number(self): with self.assertRaises(TypeError): __unit__.ensure_callable(42)
def test_number(self): with self.assertRaises(TypeError): __unit__.ensure_callable(42)
def test_callable_object(self): class Foo(object): def __call__(self_): self.fail("object must not be actually called") __unit__.ensure_callable(Foo())
def call(obj): member_func = ensure_callable(get_member_func(obj)) return member_func(*args, **kwargs)
def test_class(self): class Foo(object): def __init__(self_): self.fail("class must not be actually instantiated") __unit__.ensure_callable(Foo)
def test_none(self): with self.assertRaises(TypeError): __unit__.ensure_callable(None)
def test_function(self): def func(): self.fail("function must not be actually called") __unit__.ensure_callable(func)
def merge(arg, *rest, **kwargs): """Merge a collection, with functions as items, into a single function that takes a collection and maps its items through corresponding functions. :param arg: A collection of functions, such as list, tuple, or dictionary :param default: Optional default function to use for items within merged function's arguments that do not have corresponding functions in ``arg`` Example with two-element tuple:: >> dict_ = {'Alice': -5, 'Bob': 4} >> func = merge((str.upper, abs)) >> dict(map(func, dict_.items())) {'ALICE': 5, 'BOB': 4} Example with a dictionary:: >> func = merge({'id': int, 'name': str.split}) >> data = [ {'id': '1', 'name': "John Doe"}, {'id': '2', 'name': "Anne Arbor"}, ] >> list(map(func, data)) [{'id': 1, 'name': ['John', 'Doe']}, {'id': 2, 'name': ['Anne', 'Arbor']}] :return: Merged function .. versionadded:: 0.0.2 """ ensure_keyword_args(kwargs, optional=('default', )) has_default = 'default' in kwargs if has_default: default = ensure_callable(kwargs['default']) # if more than one argument was given, they must all be functions; # result will be a function that takes multiple arguments (rather than # a single collection) and returns a tuple unary_result = True if rest: fs = (ensure_callable(arg), ) + tuple(imap(ensure_callable, rest)) unary_result = False else: fs = arg if is_mapping(fs): if has_default: return lambda arg_: fs.__class__( (k, fs.get(k, default)(arg_[k])) for k in arg_) else: return lambda arg_: fs.__class__((k, fs[k](arg_[k])) for k in arg_) else: ensure_sequence(fs) if has_default: # we cannot use ``izip_longest(fs, arg_, fillvalue=default)``, # because we want to terminate the generator # only when ``arg_`` is exhausted (not when just ``fs`` is) func = lambda arg_: fs.__class__((fs[i] if i < len(fs) else default)(x) for i, x in enumerate(arg_)) else: # we cannot use ``izip(fs, arg_)`` because it would short-circuit # if ``arg_`` is longer than ``fs``, rather than raising # the required ``IndexError`` func = lambda arg_: fs.__class__(fs[i](x) for i, x in enumerate(arg_)) return func if unary_result else lambda *args: func(args)
def __init__(self, getter): ensure_callable(getter) self.getter = getter