exbert-project/exbert
185
1"""General programming utils, inclined toward functional programming.2 3If ever a function changes its input in place, it is denoted by a trailing `_`4"""5 6import inspect7from itertools import zip_longest8from typing import List, Set, Union, Dict9 10 11def ifnone(*xs):12 """Return the first item in 'x' that is not None"""13 for x in xs: 14 if x is not None: return x15 return None16 17def custom_dir(c, add): return dir(type(c)) + list(c.__dict__.keys()) + add18 19class GetAttr:20 """Base class for attr accesses in `self._xtra` passed down to `self.default`21 22 Taken from article by Jeremy Howard: https://www.fast.ai/2019/08/06/delegation/23 24 Usage:25 26 ```27 class ProductPage(GetAttr):28 def __init__(self, page, price, cost):29 self.page,self.price,self.cost = page,price,cost30 self.default = page31 ```32 """33 @property34 def _xtra(self): return [o for o in dir(self.default) if not o.startswith('_')]35 def __getattr__(self,k):36 if k in self._xtra: return getattr(self.default, k)37 raise AttributeError(k)38 def __dir__(self): return custom_dir(self, self._xtra)39 40# Can i delegate many different functions?41# Can i add a new docstring to the existing docstring of the delgated function? Or at least point to the function delegated?42def delegates(to=None, keep=False):43 """ Decorator: replace `**kwargs` in signature with params from `to`.44 45 Taken from article by Jeremy Howard: https://www.fast.ai/2019/08/06/delegation/46 """47 48 def _f(f):49 if to is None: to_f,from_f = f.__base__.__init__,f.__init__50 else: to_f,from_f = to,f51 sig = inspect.signature(from_f)52 sigd = dict(sig.parameters)53 k = sigd.pop('kwargs')54 s2 = {k:v for k,v in inspect.signature(to_f).parameters.items()55 if v.default != inspect.Parameter.empty and k not in sigd}56 sigd.update(s2)57 if keep: sigd['kwargs'] = k58 from_f.__signature__ = sig.replace(parameters=sigd.values())59 return f60 return _f61 62def pick(keys:Union[List, Set], obj:Dict) -> Dict:63 """ Return a NEW object containing `keys` from the original `obj` """64 return {k: obj[k] for k in keys}65 66def memoize(f):67 """Memoize a function.68 69 Use lookup table when the same inputs are passed to the function instead of running that function again70 """71 memo = {}72 def helper(*x):73 if x not in memo: 74 memo[x] = f(*x)75 return memo[x]76 return helper77 78def assoc(k, v, orig):79 """Given an original dictionary orig, return a cloned dictionary with `k` set to `v`"""80 out = orig.copy()81 out[k] = v82 return out83 84def make_unique(f):85 """The input function will only run and return if it hasn't seen its argument before. 86 87 Otherwise, it will return `None`.88 """89 s = set()90 def helper(x):91 if x in s:92 return None93 s.add(x)94 return f(x)95 96 return helper97 98def flatten_(items, seqtypes=(list, tuple)):99 """Flattten an arbitrarily nested list IN PLACE"""100 for i, x in enumerate(items):101 while i < len(items) and isinstance(items[i], seqtypes):102 items[i:i+1] = items[i]103 return items