1010 'getcallable' , 'getattrs' , 'hasattrs' , 'setattrs' , 'try_attrs' , 'GetAttrBase' , 'GetAttr' , 'delegate_attr' ,
1111 'ShowPrint' , 'Int' , 'Str' , 'Float' , 'flatten' , 'concat' , 'strcat' , 'detuplify' , 'replicate' , 'setify' ,
1212 'merge' , 'range_of' , 'groupby' , 'last_index' , 'filter_dict' , 'filter_keys' , 'filter_values' , 'cycle' ,
13- 'zip_cycle' , 'sorted_ex' , 'not_' , 'argwhere' , 'filter_ex' , 'renumerate' , 'first' , 'nested_attr' ,
14- 'nested_callable' , 'nested_idx' , 'set_nested_idx' , 'val2idx' , 'uniqueify' , 'loop_first_last' , 'loop_first ' ,
15- 'loop_last ' , 'fastuple ' , 'bind ' , 'mapt ' , 'map_ex ' , 'compose ' , 'maps ' , 'partialler ' , 'instantiate ' ,
16- 'using_attr ' , 'copy_func ' , 'patch_to ' , 'patch ' , 'patch_property ' , 'compile_re ' , 'ImportEnum ' , 'StrEnum ' ,
17- 'str_enum ' , 'Stateful ' , 'PrettyString ' , 'even_mults ' , 'num_cpus ' , 'add_props ' , 'typed ' , 'exec_new ' ,
18- 'exec_import' , 'str2bool' , 'lt' , 'gt' , 'le' , 'ge' , 'eq' , 'ne' , 'add' , 'sub' , 'mul' , 'truediv' , 'is_ ' ,
19- 'is_not' ]
13+ 'zip_cycle' , 'sorted_ex' , 'not_' , 'argwhere' , 'filter_ex' , 'renumerate' , 'first' , 'only' , ' nested_attr' ,
14+ 'nested_setdefault' , ' nested_callable' , 'nested_idx' , 'set_nested_idx' , 'val2idx' , 'uniqueify' ,
15+ 'loop_first_last ' , 'loop_first ' , 'loop_last ' , 'fastuple ' , 'bind ' , 'mapt ' , 'map_ex ' , 'compose ' , 'maps ' ,
16+ 'partialler ' , 'instantiate ' , 'using_attr ' , 'copy_func ' , 'patch_to ' , 'patch ' , 'patch_property ' , 'compile_re ' ,
17+ 'ImportEnum ' , 'StrEnum ' , 'str_enum ' , 'Stateful ' , 'PrettyString ' , 'even_mults ' , 'num_cpus ' , 'add_props ' ,
18+ 'typed' , 'exec_new' , ' exec_import' , 'str2bool' , 'lt' , 'gt' , 'le' , 'ge' , 'eq' , 'ne' , 'add' , 'sub' , 'mul' ,
19+ 'truediv' , 'is_' , ' is_not' ]
2020
2121# %% ../nbs/01_basics.ipynb 1
2222from .imports import *
@@ -651,19 +651,36 @@ def first(x, f=None, negate=False, **kwargs):
651651 return next (x , None )
652652
653653# %% ../nbs/01_basics.ipynb 263
654+ def only (o ):
655+ "Return the only item of `o`, raise if `o` doesn't have exactly one item"
656+ it = iter (o )
657+ try : res = next (it )
658+ except StopIteration : raise ValueError ('iterable has 0 items' ) from None
659+ try : next (it )
660+ except StopIteration : return res
661+ raise ValueError (f'iterable has more than 1 item' )
662+
663+ # %% ../nbs/01_basics.ipynb 265
654664def nested_attr (o , attr , default = None ):
655665 "Same as `getattr`, but if `attr` includes a `.`, then looks inside nested objects"
656666 try :
657667 for a in attr .split ("." ): o = getattr (o , a )
658668 except AttributeError : return default
659669 return o
660670
661- # %% ../nbs/01_basics.ipynb 265
671+ # %% ../nbs/01_basics.ipynb 267
672+ def nested_setdefault (o , attr , default ):
673+ "Same as `setdefault`, but if `attr` includes a `.`, then looks inside nested objects"
674+ attrs = attr .split ('.' )
675+ for a in attrs [:- 1 ]: o = o .setdefault (a , type (o )())
676+ return o .setdefault (attrs [- 1 ], default )
677+
678+ # %% ../nbs/01_basics.ipynb 271
662679def nested_callable (o , attr ):
663680 "Same as `nested_attr` but if not found will return `noop`"
664681 return nested_attr (o , attr , noop )
665682
666- # %% ../nbs/01_basics.ipynb 267
683+ # %% ../nbs/01_basics.ipynb 273
667684def _access (coll , idx ): return coll .get (idx , None ) if hasattr (coll , 'get' ) else coll [idx ] if idx < len (coll ) else None
668685
669686def _nested_idx (coll , * idxs ):
@@ -673,34 +690,34 @@ def _nested_idx(coll, *idxs):
673690 coll = coll .get (idx , None ) if hasattr (coll , 'get' ) else coll [idx ] if idx < len (coll ) else None
674691 return coll ,last_idx
675692
676- # %% ../nbs/01_basics.ipynb 268
693+ # %% ../nbs/01_basics.ipynb 274
677694def nested_idx (coll , * idxs ):
678695 "Index into nested collections, dicts, etc, with `idxs`"
679696 if not coll or not idxs : return coll
680697 coll ,idx = _nested_idx (coll , * idxs )
681698 if not coll or not idxs : return coll
682699 return _access (coll , idx )
683700
684- # %% ../nbs/01_basics.ipynb 270
701+ # %% ../nbs/01_basics.ipynb 276
685702def set_nested_idx (coll , value , * idxs ):
686703 "Set value indexed like `nested_idx"
687704 coll ,idx = _nested_idx (coll , * idxs )
688705 coll [idx ] = value
689706
690- # %% ../nbs/01_basics.ipynb 272
707+ # %% ../nbs/01_basics.ipynb 278
691708def val2idx (x ):
692709 "Dict from value to index"
693710 return {v :k for k ,v in enumerate (x )}
694711
695- # %% ../nbs/01_basics.ipynb 274
712+ # %% ../nbs/01_basics.ipynb 280
696713def uniqueify (x , sort = False , bidir = False , start = None ):
697714 "Unique elements in `x`, optional `sort`, optional return reverse correspondence, optional prepend with elements."
698715 res = list (dict .fromkeys (x ))
699716 if start is not None : res = listify (start )+ res
700717 if sort : res .sort ()
701718 return (res ,val2idx (res )) if bidir else res
702719
703- # %% ../nbs/01_basics.ipynb 276
720+ # %% ../nbs/01_basics.ipynb 282
704721# looping functions from https://github.com/willmcgugan/rich/blob/master/rich/_loop.py
705722def loop_first_last (values ):
706723 "Iterate and generate a tuple with a flag for first and last value."
@@ -713,17 +730,17 @@ def loop_first_last(values):
713730 first ,previous_value = False ,value
714731 yield first ,True ,previous_value
715732
716- # %% ../nbs/01_basics.ipynb 278
733+ # %% ../nbs/01_basics.ipynb 284
717734def loop_first (values ):
718735 "Iterate and generate a tuple with a flag for first value."
719736 return ((b ,o ) for b ,_ ,o in loop_first_last (values ))
720737
721- # %% ../nbs/01_basics.ipynb 280
738+ # %% ../nbs/01_basics.ipynb 286
722739def loop_last (values ):
723740 "Iterate and generate a tuple with a flag for last value."
724741 return ((b ,o ) for _ ,b ,o in loop_first_last (values ))
725742
726- # %% ../nbs/01_basics.ipynb 283
743+ # %% ../nbs/01_basics.ipynb 289
727744num_methods = """
728745 __add__ __sub__ __mul__ __matmul__ __truediv__ __floordiv__ __mod__ __divmod__ __pow__
729746 __lshift__ __rshift__ __and__ __xor__ __or__ __neg__ __pos__ __abs__
@@ -737,7 +754,7 @@ def loop_last(values):
737754 __ifloordiv__ __imod__ __ipow__ __ilshift__ __irshift__ __iand__ __ixor__ __ior__
738755""" .split ()
739756
740- # %% ../nbs/01_basics.ipynb 284
757+ # %% ../nbs/01_basics.ipynb 290
741758class fastuple (tuple ):
742759 "A `tuple` with elementwise ops and more friendly __init__ behavior"
743760 def __new__ (cls , x = None , * rest ):
@@ -774,7 +791,7 @@ def _f(self,*args): return self._op(op,*args)
774791setattr (fastuple ,'max' ,_get_op (max ))
775792setattr (fastuple ,'min' ,_get_op (min ))
776793
777- # %% ../nbs/01_basics.ipynb 302
794+ # %% ../nbs/01_basics.ipynb 308
778795class _Arg :
779796 def __init__ (self ,i ): self .i = i
780797arg0 = _Arg (0 )
@@ -783,7 +800,7 @@ def __init__(self,i): self.i = i
783800arg3 = _Arg (3 )
784801arg4 = _Arg (4 )
785802
786- # %% ../nbs/01_basics.ipynb 303
803+ # %% ../nbs/01_basics.ipynb 309
787804class bind :
788805 "Same as `partial`, except you can use `arg0` `arg1` etc param placeholders"
789806 def __init__ (self , func , * pargs , ** pkwargs ):
@@ -798,12 +815,12 @@ def __call__(self, *args, **kwargs):
798815 fargs = [args [x .i ] if isinstance (x , _Arg ) else x for x in self .pargs ] + args [self .maxi + 1 :]
799816 return self .func (* fargs , ** kwargs )
800817
801- # %% ../nbs/01_basics.ipynb 315
818+ # %% ../nbs/01_basics.ipynb 321
802819def mapt (func , * iterables ):
803820 "Tuplified `map`"
804821 return tuple (map (func , * iterables ))
805822
806- # %% ../nbs/01_basics.ipynb 317
823+ # %% ../nbs/01_basics.ipynb 323
807824def map_ex (iterable , f , * args , gen = False , ** kwargs ):
808825 "Like `map`, but use `bind`, and supports `str` and indexing"
809826 g = (bind (f ,* args ,** kwargs ) if callable (f )
@@ -813,7 +830,7 @@ def map_ex(iterable, f, *args, gen=False, **kwargs):
813830 if gen : return res
814831 return list (res )
815832
816- # %% ../nbs/01_basics.ipynb 325
833+ # %% ../nbs/01_basics.ipynb 331
817834def compose (* funcs , order = None ):
818835 "Create a function that composes all functions in `funcs`, passing along remaining `*args` and `**kwargs` to all"
819836 funcs = listify (funcs )
@@ -825,14 +842,14 @@ def _inner(x, *args, **kwargs):
825842 return x
826843 return _inner
827844
828- # %% ../nbs/01_basics.ipynb 327
845+ # %% ../nbs/01_basics.ipynb 333
829846def maps (* args , retain = noop ):
830847 "Like `map`, except funcs are composed first"
831848 f = compose (* args [:- 1 ])
832849 def _f (b ): return retain (f (b ), b )
833850 return map (_f , args [- 1 ])
834851
835- # %% ../nbs/01_basics.ipynb 329
852+ # %% ../nbs/01_basics.ipynb 335
836853def partialler (f , * args , order = None , ** kwargs ):
837854 "Like `functools.partial` but also copies over docstring"
838855 fnew = partial (f ,* args ,** kwargs )
@@ -841,20 +858,20 @@ def partialler(f, *args, order=None, **kwargs):
841858 elif hasattr (f ,'order' ): fnew .order = f .order
842859 return fnew
843860
844- # %% ../nbs/01_basics.ipynb 333
861+ # %% ../nbs/01_basics.ipynb 339
845862def instantiate (t ):
846863 "Instantiate `t` if it's a type, otherwise do nothing"
847864 return t () if isinstance (t , type ) else t
848865
849- # %% ../nbs/01_basics.ipynb 335
866+ # %% ../nbs/01_basics.ipynb 341
850867def _using_attr (f , attr , x ): return f (getattr (x ,attr ))
851868
852- # %% ../nbs/01_basics.ipynb 336
869+ # %% ../nbs/01_basics.ipynb 342
853870def using_attr (f , attr ):
854871 "Construct a function which applies `f` to the argument's attribute `attr`"
855872 return partial (_using_attr , f , attr )
856873
857- # %% ../nbs/01_basics.ipynb 340
874+ # %% ../nbs/01_basics.ipynb 346
858875class _Self :
859876 "An alternative to `lambda` for calling methods on passed object."
860877 def __init__ (self ): self .nms ,self .args ,self .kwargs ,self .ready = [],[],[],True
@@ -886,18 +903,18 @@ def _call(self, *args, **kwargs):
886903 self .ready = True
887904 return self
888905
889- # %% ../nbs/01_basics.ipynb 341
906+ # %% ../nbs/01_basics.ipynb 347
890907class _SelfCls :
891908 def __getattr__ (self ,k ): return getattr (_Self (),k )
892909 def __getitem__ (self ,i ): return self .__getattr__ ('__getitem__' )(i )
893910 def __call__ (self ,* args ,** kwargs ): return self .__getattr__ ('_call' )(* args ,** kwargs )
894911
895912Self = _SelfCls ()
896913
897- # %% ../nbs/01_basics.ipynb 342
914+ # %% ../nbs/01_basics.ipynb 348
898915_all_ = ['Self' ]
899916
900- # %% ../nbs/01_basics.ipynb 348
917+ # %% ../nbs/01_basics.ipynb 354
901918def copy_func (f ):
902919 "Copy a non-builtin function (NB `copy.copy` does not work for this)"
903920 if not isinstance (f ,FunctionType ): return copy (f )
@@ -908,7 +925,7 @@ def copy_func(f):
908925 fn .__qualname__ = f .__qualname__
909926 return fn
910927
911- # %% ../nbs/01_basics.ipynb 354
928+ # %% ../nbs/01_basics.ipynb 360
912929def patch_to (cls , as_prop = False , cls_method = False ):
913930 "Decorator: add `f` to `cls`"
914931 if not isinstance (cls , (tuple ,list )): cls = (cls ,)
@@ -927,45 +944,45 @@ def _inner(f):
927944 return globals ().get (nm , builtins .__dict__ .get (nm , None ))
928945 return _inner
929946
930- # %% ../nbs/01_basics.ipynb 365
947+ # %% ../nbs/01_basics.ipynb 371
931948def patch (f = None , * , as_prop = False , cls_method = False ):
932949 "Decorator: add `f` to the first parameter's class (based on f's type annotations)"
933950 if f is None : return partial (patch , as_prop = as_prop , cls_method = cls_method )
934951 ann ,glb ,loc = get_annotations_ex (f )
935952 cls = union2tuple (eval_type (ann .pop ('cls' ) if cls_method else next (iter (ann .values ())), glb , loc ))
936953 return patch_to (cls , as_prop = as_prop , cls_method = cls_method )(f )
937954
938- # %% ../nbs/01_basics.ipynb 373
955+ # %% ../nbs/01_basics.ipynb 379
939956def patch_property (f ):
940957 "Deprecated; use `patch(as_prop=True)` instead"
941958 warnings .warn ("`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead" )
942959 cls = next (iter (f .__annotations__ .values ()))
943960 return patch_to (cls , as_prop = True )(f )
944961
945- # %% ../nbs/01_basics.ipynb 375
962+ # %% ../nbs/01_basics.ipynb 381
946963def compile_re (pat ):
947964 "Compile `pat` if it's not None"
948965 return None if pat is None else re .compile (pat )
949966
950- # %% ../nbs/01_basics.ipynb 377
967+ # %% ../nbs/01_basics.ipynb 383
951968class ImportEnum (enum .Enum ):
952969 "An `Enum` that can have its values imported"
953970 @classmethod
954971 def imports (cls ):
955972 g = sys ._getframe (1 ).f_locals
956973 for o in cls : g [o .name ]= o
957974
958- # %% ../nbs/01_basics.ipynb 380
975+ # %% ../nbs/01_basics.ipynb 386
959976class StrEnum (str ,ImportEnum ):
960977 "An `ImportEnum` that behaves like a `str`"
961978 def __str__ (self ): return self .name
962979
963- # %% ../nbs/01_basics.ipynb 382
980+ # %% ../nbs/01_basics.ipynb 388
964981def str_enum (name , * vals ):
965982 "Simplified creation of `StrEnum` types"
966983 return StrEnum (name , {o :o for o in vals })
967984
968- # %% ../nbs/01_basics.ipynb 384
985+ # %% ../nbs/01_basics.ipynb 390
969986class Stateful :
970987 "A base class/mixin for objects that should not serialize all their state"
971988 _stateattrs = ()
@@ -985,37 +1002,37 @@ def _init_state(self):
9851002 "Override for custom init and deserialization logic"
9861003 self ._state = {}
9871004
988- # %% ../nbs/01_basics.ipynb 390
1005+ # %% ../nbs/01_basics.ipynb 396
9891006class PrettyString (str ):
9901007 "Little hack to get strings to show properly in Jupyter."
9911008 def __repr__ (self ): return self
9921009
993- # %% ../nbs/01_basics.ipynb 396
1010+ # %% ../nbs/01_basics.ipynb 402
9941011def even_mults (start , stop , n ):
9951012 "Build log-stepped array from `start` to `stop` in `n` steps."
9961013 if n == 1 : return stop
9971014 mult = stop / start
9981015 step = mult ** (1 / (n - 1 ))
9991016 return [start * (step ** i ) for i in range (n )]
10001017
1001- # %% ../nbs/01_basics.ipynb 398
1018+ # %% ../nbs/01_basics.ipynb 404
10021019def num_cpus ():
10031020 "Get number of cpus"
10041021 try : return len (os .sched_getaffinity (0 ))
10051022 except AttributeError : return os .cpu_count ()
10061023
10071024defaults .cpus = num_cpus ()
10081025
1009- # %% ../nbs/01_basics.ipynb 400
1026+ # %% ../nbs/01_basics.ipynb 406
10101027def add_props (f , g = None , n = 2 ):
10111028 "Create properties passing each of `range(n)` to f"
10121029 if g is None : return (property (partial (f ,i )) for i in range (n ))
10131030 return (property (partial (f ,i ), partial (g ,i )) for i in range (n ))
10141031
1015- # %% ../nbs/01_basics.ipynb 403
1032+ # %% ../nbs/01_basics.ipynb 409
10161033def _typeerr (arg , val , typ ): return TypeError (f"{ arg } =={ val } not { typ } " )
10171034
1018- # %% ../nbs/01_basics.ipynb 404
1035+ # %% ../nbs/01_basics.ipynb 410
10191036def typed (f ):
10201037 "Decorator to check param and return types at runtime"
10211038 names = f .__code__ .co_varnames
@@ -1032,21 +1049,21 @@ def _f(*args,**kwargs):
10321049 return res
10331050 return functools .update_wrapper (_f , f )
10341051
1035- # %% ../nbs/01_basics.ipynb 412
1052+ # %% ../nbs/01_basics.ipynb 418
10361053def exec_new (code ):
10371054 "Execute `code` in a new environment and return it"
10381055 pkg = None if __name__ == '__main__' else Path ().cwd ().name
10391056 g = {'__name__' : __name__ , '__package__' : pkg }
10401057 exec (code , g )
10411058 return g
10421059
1043- # %% ../nbs/01_basics.ipynb 414
1060+ # %% ../nbs/01_basics.ipynb 420
10441061def exec_import (mod , sym ):
10451062 "Import `sym` from `mod` in a new environment"
10461063# pref = '' if __name__=='__main__' or mod[0]=='.' else '.'
10471064 return exec_new (f'from { mod } import { sym } ' )
10481065
1049- # %% ../nbs/01_basics.ipynb 415
1066+ # %% ../nbs/01_basics.ipynb 421
10501067def str2bool (s ):
10511068 "Case-insensitive convert string `s` too a bool (`y`,`yes`,`t`,`true`,`on`,`1`->`True`)"
10521069 if not isinstance (s ,str ): return bool (s )
0 commit comments