None,1), 1)
test_eq(ifnone(2 ,1), 2) test_eq(ifnone(
Basic functionality
Basics
ifnone
ifnone (a, b)
b
if a
is None else a
Since b if a is None else a
is such a common pattern, we wrap it in a function. However, be careful, because python will evaluate both a
and b
when calling ifnone
(which it doesn’t do if using the if
version directly).
maybe_attr
maybe_attr (o, attr)
getattr(o,attr,o)
Return the attribute attr
for object o
. If the attribute doesn’t exist, then return the object o
instead.
class myobj: myattr='foo'
'myattr'), 'foo')
test_eq(maybe_attr(myobj, 'another_attr'), myobj) test_eq(maybe_attr(myobj,
basic_repr
basic_repr (flds=None)
Minimal __repr__
In types which provide rich display functionality in Jupyter, their __repr__
is also called in order to provide a fallback text representation. Unfortunately, this includes a memory address which changes on every invocation, making it non-deterministic. This causes diffs to get messy and creates conflicts in git. To fix this, put __repr__=basic_repr()
inside your class.
class SomeClass: __repr__=basic_repr()
repr(SomeClass())
'SomeClass()'
If you pass a list of attributes (flds
) of an object, then this will generate a string with the name of each attribute and its corresponding value. The format of this string is key=value
, where key
is the name of the attribute, and value
is the value of the attribute. For each value, attempt to use the __name__
attribute, otherwise fall back to using the value’s __repr__
when constructing the string.
class SomeClass:
=1
a='foo'
b__repr__=basic_repr('a,b')
__name__='some-class'
repr(SomeClass())
"SomeClass(a=1, b='foo')"
Nested objects work too:
class AnotherClass:
=SomeClass()
c='bar'
d__repr__=basic_repr(['c', 'd'])
repr(AnotherClass())
"AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')"
Instance variables (but not class variables) are shown if basic_repr
is called with no arguments:
class SomeClass:
def __init__(self, a=1, b='foo'): self.a,self.b = a,b
__repr__=basic_repr()
repr(SomeClass())
"SomeClass(a=1, b='foo')"
BasicRepr
BasicRepr ()
Base class for objects needing a basic __repr__
As a shortcut for creating a __repr__
for instance variables, you can inherit from BasicRepr
:
class SomeClass(BasicRepr):
def __init__(self, a=1, b='foo'): self.a,self.b = a,b
repr(SomeClass())
"SomeClass(a=1, b='foo')"
is_array
is_array (x)
True
if x
supports __array__
or iloc
1)),is_array([1]) is_array(np.array(
(True, False)
listify
listify (o=None, *rest, use_list=False, match=None)
Convert o
to a list
Conversion is designed to “do what you mean”, e.g:
'hi'), ['hi'])
test_eq(listify(b'hi'), [b'hi'])
test_eq(listify(1)), [array(1)])
test_eq(listify(array(1), [1])
test_eq(listify(1,2]), [1,2])
test_eq(listify([range(3)), [0,1,2])
test_eq(listify(None), [])
test_eq(listify(1,2), [1,2]) test_eq(listify(
= np.arange(9).reshape(3,3)
arr listify(arr)
[array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])]
1,2])) listify(array([
[array([1, 2])]
Generators are turned into lists too:
= (o for o in range(3))
gen 0,1,2]) test_eq(listify(gen), [
Use match
to provide a length to match:
1,match=3), [1,1,1]) test_eq(listify(
If match
is a sequence, it’s length is used:
1,match=range(3)), [1,1,1]) test_eq(listify(
If the listified item is not of length 1
, it must be the same length as match
:
1,1,1],match=3), [1,1,1])
test_eq(listify([lambda: listify([1,1],match=3)) test_fail(
tuplify
tuplify (o, use_list=False, match=None)
Make o
a tuple
None),())
test_eq(tuplify(1,2,3]),(1,2,3))
test_eq(tuplify([1,match=[1,2,3]),(1,1,1)) test_eq(tuplify(
true
true (x)
Test whether x
is truthy; collections with >0 elements are considered True
for o in
[(o,true(o)) 0),array(1),array([0]),array([0,1]),1,0,'',None)] (array(
[(array(0), False),
(array(1), True),
(array([0]), True),
(array([0, 1]), True),
(1, True),
(0, False),
('', False),
(None, False)]
NullType
NullType ()
An object that is False
and can be called, chained, and indexed
bool(null.hi().there[3])
False
tonull
tonull (x)
Convert None
to null
bool(tonull(None).hi().there[3])
False
get_class
get_class (nm, *fld_names, sup=None, doc=None, funcs=None, anno=None, **flds)
Dynamically create a class, optionally inheriting from sup
, containing fld_names
= get_class('_t', 'a', b=2, anno={'b':int})
_t = _t()
t None)
test_eq(t.a, 2)
test_eq(t.b, = _t(1, b=3)
t 1)
test_eq(t.a, 3)
test_eq(t.b, = _t(1, 3)
t 1)
test_eq(t.a, 3)
test_eq(t.b,
test_eq(t, pickle.loads(pickle.dumps(t)))'b':int, 'a':typing.Any})
test_eq(_t.__annotations__, {repr(t)
'__main__._t(a=1, b=3)'
Most often you’ll want to call mk_class
, since it adds the class to your module. See mk_class
for more details and examples of use (which also apply to get_class
).
mk_class
mk_class (nm, *fld_names, sup=None, doc=None, funcs=None, mod=None, anno=None, **flds)
Create a class using get_class
and add to the caller’s module
Any kwargs
will be added as class attributes, and sup
is an optional (tuple of) base classes.
'_t', a=1, sup=dict)
mk_class(= _t()
t 1)
test_eq(t.a, assert(isinstance(t,dict))
A __init__
is provided that sets attrs for any kwargs
, and for any args
(matching by position to fields), along with a __repr__
which prints all attrs. The docstring is set to doc
. You can pass funcs
which will be added as attrs with the function names.
def foo(self): return 1
'_t', 'a', sup=dict, doc='test doc', funcs=foo)
mk_class(
= _t(3, b=2)
t 3)
test_eq(t.a, 2)
test_eq(t.b, 1)
test_eq(t.foo(), 'test doc')
test_eq(t.__doc__, t
{}
wrap_class
wrap_class (nm, *fld_names, sup=None, doc=None, funcs=None, **flds)
Decorator: makes function a method of a new class nm
passing parameters to mk_class
@wrap_class('_t', a=2)
def bar(self,x): return x+1
= _t()
t 2)
test_eq(t.a, 3), 4) test_eq(t.bar(
ignore_exceptions
ignore_exceptions ()
Context manager to ignore exceptions
with ignore_exceptions():
# Exception will be ignored
raise Exception
exec_local
exec_local (code, var_name)
Call exec
on code
and return the var var_name
"a=1", "a"), 1) test_eq(exec_local(
risinstance
risinstance (types, obj=None)
Curried isinstance
but with args reversed
assert risinstance(int, 1)
assert not risinstance(str, 0)
assert risinstance(int)(1)
assert not risinstance(int)(None)
types
can also be strings:
assert risinstance(('str','int'), 'a')
assert risinstance('str', 'a')
assert not risinstance('int', 'a')
ver2tuple
ver2tuple (v:str)
'3.8.1'), (3,8,1))
test_eq(ver2tuple('3.1'), (3,1,0))
test_eq(ver2tuple('3.'), (3,0,0))
test_eq(ver2tuple('3'), (3,0,0)) test_eq(ver2tuple(
NoOp
These are used when you need a pass-through function.
noop
noop (x=None, *args, **kwargs)
Do nothing
noop()1),1) test_eq(noop(
noops
noops (x=None, *args, **kwargs)
Do nothing (method)
class _t: foo=noops
1),1) test_eq(_t().foo(
Infinite Lists
These lists are useful for things like padding an array or adding index column(s) to arrays.
Inf
defines the following properties:
count: itertools.count()
zeros: itertools.cycle([0])
ones : itertools.cycle([1])
nones: itertools.cycle([None])
for i,o in zip(range(5), Inf.count)],
test_eq([o 0, 1, 2, 3, 4])
[
for i,o in zip(range(5), Inf.zeros)],
test_eq([o 0]*5)
[
for i,o in zip(range(5), Inf.ones)],
test_eq([o 1]*5)
[
for i,o in zip(range(5), Inf.nones)],
test_eq([o None]*5) [
Operator Functions
in_
in_ (x, a)
True
if x in a
# test if element is in another
assert in_('c', ('b', 'c', 'a'))
assert in_(4, [2,3,4,5])
assert in_('t', 'fastai')
'h', 'fastai'))
test_fail(in_(
# use in_ as a partial
assert in_('fastai')('t')
assert in_([2,3,4,5])(4)
'fastai')('h')) test_fail(in_(
In addition to in_
, the following functions are provided matching the behavior of the equivalent versions in operator
: lt gt le ge eq ne add sub mul truediv is_ is_not mod.
3,5),gt(3,5),is_(None,None),in_(0,[1,2]),mod(3,2) lt(
(True, False, True, False, 1)
Similarly to _in
, they also have additional functionality: if you only pass one param, they return a partial function that passes that param as the second positional parameter.
5)(3),gt(5)(3),is_(None)(None),in_([1,2])(0),mod(2)(3) lt(
(True, False, True, False, 1)
ret_true
ret_true (*args, **kwargs)
Predicate: always True
assert ret_true(1,2,3)
assert ret_true(False)
ret_false
ret_false (*args, **kwargs)
Predicate: always False
stop
stop (e=<class 'StopIteration'>)
Raises exception e
(by default StopIteration
)
gen
gen (func, seq, cond=<function ret_true>)
Like (func(o) for o in seq if cond(func(o)))
but handles StopIteration
5)),
test_eq(gen(noop, Inf.count, lt(range(5))
-5)),
test_eq(gen(operator.neg, Inf.count, gt(0,-1,-2,-3,-4])
[lambda o:o if o<5 else stop(), Inf.count),
test_eq(gen(range(5))
chunked
chunked (it, chunk_sz=None, drop_last=False, n_chunks=None)
Return batches from iterator it
of size chunk_sz
(or return n_chunks
total)
Note that you must pass either chunk_sz
, or n_chunks
, but not both.
= list(range(10))
t 3), [[0,1,2], [3,4,5], [6,7,8], [9]])
test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ])
test_eq(chunked(t,
= map(lambda o:stop() if o==6 else o, Inf.count)
t 3), [[0, 1, 2], [3, 4, 5]])
test_eq(chunked(t,= map(lambda o:stop() if o==7 else o, Inf.count)
t 3), [[0, 1, 2], [3, 4, 5], [6]])
test_eq(chunked(t,
= np.arange(10)
t 3), [[0,1,2], [3,4,5], [6,7,8], [9]])
test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ])
test_eq(chunked(t,
3), [])
test_eq(chunked([], =3), []) test_eq(chunked([], n_chunks
otherwise
otherwise (x, tst, y)
y if tst(x) else x
2+1, gt(3), 4), 3)
test_eq(otherwise(2+1, gt(2), 4), 4) test_eq(otherwise(
Attribute Helpers
These functions reduce boilerplate when setting or manipulating attributes or properties of objects.
custom_dir
custom_dir (c, add)
Implement custom __dir__
, adding add
to cls
custom_dir
allows you extract the __dict__
property of a class and appends the list add
to it.
class _T:
def f(): pass
= custom_dir(_T(), add=['foo', 'bar'])
s assert {'foo', 'bar', 'f'}.issubset(s)
AttrDict
dict
subclass that also provides access to keys as attrs
= AttrDict(a=1,b="two")
d 1)
test_eq(d.a, 'b'], 'two')
test_eq(d['c','nope'), 'nope')
test_eq(d.get(= 2
d.b 2)
test_eq(d.b, 'b'], 2)
test_eq(d['b'] = 3
d['b'], 3)
test_eq(d[3)
test_eq(d.b, assert 'a' in dir(d)
AttrDict
will pretty print in Jupyter Notebooks:
= {'a':1, 'b': {'c':1, 'd':2}, 'c': {'c':1, 'd':2}, 'd': {'c':1, 'd':2},
_test_dict 'e': {'c':1, 'd':2}, 'f': {'c':1, 'd':2, 'e': 4, 'f':[1,2,3,4,5]}}
AttrDict(_test_dict)
{ 'a': 1,
'b': {'c': 1, 'd': 2},
'c': {'c': 1, 'd': 2},
'd': {'c': 1, 'd': 2},
'e': {'c': 1, 'd': 2},
'f': {'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}}
AttrDictDefault
AttrDictDefault (*args, default_=None, **kwargs)
AttrDict
subclass that returns None
for missing attrs
= AttrDictDefault(a=1,b="two", default_='nope')
d 1)
test_eq(d.a, 'b'], 'two')
test_eq(d['nope') test_eq(d.c,
NS
SimpleNamespace
subclass that also adds iter
and dict
support
This is very similar to AttrDict
, but since it starts with SimpleNamespace
, it has some differences in behavior. You can use it just like SimpleNamespace
:
= NS(**_test_dict)
d d
namespace(a=1,
b={'c': 1, 'd': 2},
c={'c': 1, 'd': 2},
d={'c': 1, 'd': 2},
e={'c': 1, 'd': 2},
f={'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]})
…but you can also index it to get/set:
'a'] d[
1
…and iterate t:
list(d)
['a', 'b', 'c', 'd', 'e', 'f']
get_annotations_ex
get_annotations_ex (obj, globals=None, locals=None)
Backport of py3.10 get_annotations
that returns globals/locals
In Python 3.10 inspect.get_annotations
was added. However previous versions of Python are unable to evaluate type annotations correctly if from future import __annotations__
is used. Furthermore, all annotations are evaluated, even if only some subset are needed. get_annotations_ex
provides the same functionality as inspect.get_annotations
, but works on earlier versions of Python, and returns the globals
and locals
needed to evaluate types.
eval_type
eval_type (t, glb, loc)
eval
a type or collection of types, if needed, for annotations in py3.10+
In py3.10, or if from future import __annotations__
is used, a
is a str
:
class _T2a: pass
def func(a: _T2a): pass
= get_annotations_ex(func)
ann,glb,loc
'a'], glb, loc) eval_type(ann[
__main__._T2a
|
is supported for defining Union
types when using eval_type
even for python versions prior to 3.9:
class _T2b: pass
def func(a: _T2a|_T2b): pass
= get_annotations_ex(func)
ann,glb,loc
'a'], glb, loc) eval_type(ann[
typing.Union[__main__._T2a, __main__._T2b]
type_hints
type_hints (f)
Like typing.get_type_hints
but returns {}
if not allowed type
Below is a list of allowed types for type hints in python:
list(typing._allowed_types)
[function,
builtin_function_or_method,
method,
module,
wrapper_descriptor,
method-wrapper,
method_descriptor]
For example, type func
is allowed so type_hints
returns the same value as typing.get_hints
:
def f(a:int)->bool: ... # a function with type hints (allowed)
= {'a':int,'return':bool}
exp
test_eq(type_hints(f), typing.get_type_hints(f)) test_eq(type_hints(f), exp)
However, class
is not an allowed type, so type_hints
returns {}
:
class _T:
def __init__(self, a:int=0)->bool: ...
assert not type_hints(_T)
annotations
annotations (o)
Annotations for o
, or type(o)
This supports a wider range of situations than type_hints
, by checking type()
and __init__
for annotations too:
for o in _T,_T(),_T.__init__,f: test_eq(annotations(o), exp)
assert not annotations(int)
assert not annotations(print)
anno_ret
anno_ret (func)
Get the return annotation of func
def f(x) -> float: return x
float)
test_eq(anno_ret(f),
def f(x) -> typing.Tuple[float,float]: return x
assert anno_ret(f)==typing.Tuple[float,float]
If your return annotation is None
, anno_ret
will return NoneType
(and not None
):
def f(x) -> None: return x
test_eq(anno_ret(f), NoneType)assert anno_ret(f) is not None # returns NoneType instead of None
If your function does not have a return type, or if you pass in None
instead of a function, then anno_ret
returns None
:
def f(x): return x
None)
test_eq(anno_ret(f), None), None) # instead of passing in a func, pass in None test_eq(anno_ret(
signature_ex
signature_ex (obj, eval_str:bool=False)
Backport of inspect.signature(..., eval_str=True
to <py310
union2tuple
union2tuple (t)
int,str]), (int,str))
test_eq(union2tuple(Union[int), int)
test_eq(union2tuple(assert union2tuple(Tuple[int,str])==Tuple[int,str]
int,str)), (int,str))
test_eq(union2tuple((if UnionType: test_eq(union2tuple(int|str), (int,str))
argnames
argnames (f, frame=False)
Names of arguments to function or frame f
'x']) test_eq(argnames(f), [
with_cast
with_cast (f)
Decorator which uses any parameter annotations as preprocessing functions
@with_cast
def _f(a, b:Path, c:str='', d=0): return (a,b,c,d)
1, '.', 3), (1,Path('.'),'3',0))
test_eq(_f(1, '.'), (1,Path('.'),'',0))
test_eq(_f(
@with_cast
def _g(a:int=0)->str: return a
4.0), '4')
test_eq(_g(4.4), '4')
test_eq(_g(2), '2') test_eq(_g(
store_attr
store_attr (names=None, but='', cast=False, store_args=None, **attrs)
Store params named in comma-separated names
from calling context into attrs in self
In it’s most basic form, you can use store_attr
to shorten code like this:
class T:
def __init__(self, a,b,c): self.a,self.b,self.c = a,b,c
…to this:
class T:
def __init__(self, a,b,c): store_attr('a,b,c', self)
This class behaves as if we’d used the first form:
= T(1,c=2,b=3)
t assert t.a==1 and t.b==3 and t.c==2
class T1:
def __init__(self, a,b,c): store_attr()
In addition, it stores the attrs as a dict
in __stored_args__
, which you can use for display, logging, and so forth.
'a':1, 'b':3, 'c':2}) test_eq(t.__stored_args__, {
Since you normally want to use the first argument (often called self
) for storing attributes, it’s optional:
class T:
def __init__(self, a,b,c:str): store_attr('a,b,c')
= T(1,c=2,b=3)
t assert t.a==1 and t.b==3 and t.c==2
With cast=True
any parameter annotations will be used as preprocessing functions for the corresponding arguments:
class T:
def __init__(self, a:listify, b, c:str): store_attr('a,b,c', cast=True)
= T(1,c=2,b=3)
t assert t.a==[1] and t.b==3 and t.c=='2'
You can inherit from a class using store_attr
, and just call it again to add in any new attributes added in the derived class:
class T2(T):
def __init__(self, d, **kwargs):
super().__init__(**kwargs)
'd')
store_attr(
= T2(d=1,a=2,b=3,c=4)
t assert t.a==2 and t.b==3 and t.c==4 and t.d==1
You can skip passing a list of attrs to store. In this case, all arguments passed to the method are stored:
class T:
def __init__(self, a,b,c): store_attr()
= T(1,c=2,b=3)
t assert t.a==1 and t.b==3 and t.c==2
class T4(T):
def __init__(self, d, **kwargs):
super().__init__(**kwargs)
store_attr()
= T4(4, a=1,c=2,b=3)
t assert t.a==1 and t.b==3 and t.c==2 and t.d==4
class T4:
def __init__(self, *, a: int, b: float = 1):
store_attr()
= T4(a=3)
t assert t.a==3 and t.b==1
= T4(a=3, b=2)
t assert t.a==3 and t.b==2
You can skip some attrs by passing but
:
class T:
def __init__(self, a,b,c): store_attr(but='a')
= T(1,c=2,b=3)
t assert t.b==3 and t.c==2
assert not hasattr(t,'a')
You can also pass keywords to store_attr
, which is identical to setting the attrs directly, but also stores them in __stored_args__
.
class T:
def __init__(self): store_attr(a=1)
= T()
t assert t.a==1
You can also use store_attr inside functions.
def create_T(a, b):
= SimpleNamespace()
t self=t)
store_attr(return t
= create_T(a=1, b=2)
t assert t.a==1 and t.b==2
attrdict
attrdict (o, *ks, default=None)
Dict from each k
in ks
to getattr(o,k)
class T:
def __init__(self, a,b,c): store_attr()
= T(1,c=2,b=3)
t 'b','c'), {'b':3, 'c':2}) test_eq(attrdict(t,
properties
properties (cls, *ps)
Change attrs in cls
with names in ps
to properties
class T:
def a(self): return 1
def b(self): return 2
'a')
properties(T,
1)
test_eq(T().a,2) test_eq(T().b(),
camel2words
camel2words (s, space=' ')
Convert CamelCase to ‘spaced words’
'ClassAreCamel'), 'Class Are Camel') test_eq(camel2words(
camel2snake
camel2snake (name)
Convert CamelCase to snake_case
'ClassAreCamel'), 'class_are_camel')
test_eq(camel2snake('Already_Snake'), 'already__snake') test_eq(camel2snake(
snake2camel
snake2camel (s)
Convert snake_case to CamelCase
'a_b_cc'), 'ABCc') test_eq(snake2camel(
class2attr
class2attr (cls_name)
Return the snake-cased name of the class; strip ending cls_name
if it exists.
class Parent:
@property
def name(self): return class2attr(self, 'Parent')
class ChildOfParent(Parent): pass
class ParentChildOf(Parent): pass
= Parent()
p = ChildOfParent()
cp = ParentChildOf()
cp2
'parent')
test_eq(p.name, 'child_of')
test_eq(cp.name, 'parent_child_of') test_eq(cp2.name,
getcallable
getcallable (o, attr)
Calls getattr
with a default of noop
class Math:
def addition(self,a,b): return a+b
= Math()
m
"addition")(a=1,b=2), 3)
test_eq(getcallable(m, "subtraction")(a=1,b=2), None) test_eq(getcallable(m,
getattrs
getattrs (o, *attrs, default=None)
List of all attrs
in o
from fractions import Fraction
1,2), 'numerator', 'denominator') getattrs(Fraction(
[1, 2]
hasattrs
hasattrs (o, attrs)
Test whether o
contains all attrs
assert hasattrs(1,('imag','real'))
assert not hasattrs(1,('imag','foo'))
setattrs
setattrs (dest, flds, src)
= dict(a=1,bb="2",ignore=3)
d = SimpleNamespace()
o "a,bb", d)
setattrs(o, 1)
test_eq(o.a, "2") test_eq(o.bb,
= SimpleNamespace(a=1,bb="2",ignore=3)
d = SimpleNamespace()
o "a,bb", d)
setattrs(o, 1)
test_eq(o.a, "2") test_eq(o.bb,
try_attrs
try_attrs (obj, *attrs)
Return first attr that exists in obj
1, 'real'), 1)
test_eq(try_attrs(1, 'foobar', 'real'), 1) test_eq(try_attrs(
Attribute Delegation
GetAttrBase
GetAttrBase ()
Basic delegation of __getattr__
and __dir__
GetAttr
GetAttr ()
Inherit from this to have all attr accesses in self._xtra
passed down to self.default
Inherit from GetAttr
to have attr access passed down to an instance attribute. This makes it easy to create composites that don’t require callers to know about their components. For a more detailed discussion of how this works as well as relevant context, we suggest reading the delegated composition section of this blog article.
You can customise the behaviour of GetAttr
in subclasses via; - _default
- By default, this is set to 'default'
, so attr access is passed down to self.default
- _default
can be set to the name of any instance attribute that does not start with dunder __
- _xtra
- By default, this is None
, so all attr access is passed down - You can limit which attrs get passed down by setting _xtra
to a list of attribute names
To illuminate the utility of GetAttr
, suppose we have the following two classes, _WebPage
which is a superclass of _ProductPage
, which we wish to compose like so:
class _WebPage:
def __init__(self, title, author="Jeremy"):
self.title,self.author = title,author
class _ProductPage:
def __init__(self, page, price): self.page,self.price = page,price
= _WebPage('Soap', author="Sylvain")
page = _ProductPage(page, 15.0) p
How do we make it so we can just write p.author
, instead of p.page.author
to access the author
attribute? We can use GetAttr
, of course! First, we subclass GetAttr
when defining _ProductPage
. Next, we set self.default
to the object whose attributes we want to be able to access directly, which in this case is the page
argument passed on initialization:
class _ProductPage(GetAttr):
def __init__(self, page, price): self.default,self.price = page,price #self.default allows you to access page directly.
= _ProductPage(page, 15.0) p
Now, we can access the author
attribute directly from the instance:
'Sylvain') test_eq(p.author,
If you wish to store the object you are composing in an attribute other than self.default
, you can set the class attribute _data
as shown below. This is useful in the case where you might have a name collision with self.default
:
class _C(GetAttr):
= '_data' # use different component name; `self._data` rather than `self.default`
_default def __init__(self,a): self._data = a
def foo(self): noop
= _C('Hi')
t 'Hi')
test_eq(t._data, lambda: t.default) # we no longer have self.default
test_fail('hi')
test_eq(t.lower(), 'HI')
test_eq(t.upper(), assert 'lower' in dir(t)
assert 'upper' in dir(t)
By default, all attributes and methods of the object you are composing are retained. In the below example, we compose a str
object with the class _C
. This allows us to directly call string methods on instances of class _C
, such as str.lower()
or str.upper()
:
class _C(GetAttr):
# allow all attributes and methods to get passed to `self.default` (by leaving _xtra=None)
def __init__(self,a): self.default = a
def foo(self): noop
= _C('Hi')
t 'hi')
test_eq(t.lower(), 'HI')
test_eq(t.upper(), assert 'lower' in dir(t)
assert 'upper' in dir(t)
However, you can choose which attributes or methods to retain by defining a class attribute _xtra
, which is a list of allowed attribute and method names to delegate. In the below example, we only delegate the lower
method from the composed str
object when defining class _C
:
class _C(GetAttr):
= ['lower'] # specify which attributes get passed to `self.default`
_xtra def __init__(self,a): self.default = a
def foo(self): noop
= _C('Hi')
t 'Hi')
test_eq(t.default, 'hi')
test_eq(t.lower(), lambda: t.upper()) # upper wasn't in _xtra, so it isn't available to be called
test_fail(assert 'lower' in dir(t)
assert 'upper' not in dir(t)
You must be careful to properly set an instance attribute in __init__
that corresponds to the class attribute _default
. The below example sets the class attribute _default
to data
, but erroneously fails to define self.data
(and instead defines self.default
).
Failing to properly set instance attributes leads to errors when you try to access methods directly:
class _C(GetAttr):
= 'data' # use a bad component name; i.e. self.data does not exist
_default def __init__(self,a): self.default = a
def foo(self): noop
# TODO: should we raise an error when we create a new instance ...
= _C('Hi')
t 'Hi')
test_eq(t.default, # ... or is it enough for all GetAttr features to raise errors
lambda: t.data)
test_fail(lambda: t.lower())
test_fail(lambda: t.upper())
test_fail(lambda: dir(t)) test_fail(
delegate_attr
delegate_attr (k, to)
Use in __getattr__
to delegate to attr to
without inheriting from GetAttr
delegate_attr
is a functional way to delegate attributes, and is an alternative to GetAttr
. We recommend reading the documentation of GetAttr
for more details around delegation.
You can use achieve delegation when you define __getattr__
by using delegate_attr
:
class _C:
def __init__(self, o): self.o = o # self.o corresponds to the `to` argument in delegate_attr.
def __getattr__(self, k): return delegate_attr(self, k, to='o')
= _C('HELLO') # delegates to a string
t 'hello')
test_eq(t.lower(),
= _C(np.array([5,4,3])) # delegates to a numpy array
t sum(), 12)
test_eq(t.
= _C(pd.DataFrame({'a': [1,2], 'b': [3,4]})) # delegates to a pandas.DataFrame
t max(), 4) test_eq(t.b.
Extensible Types
ShowPrint
is a base class that defines a show
method, which is used primarily for callbacks in fastai that expect this method to be defined.
Int
, Float
, and Str
extend int
, float
and str
respectively by adding an additional show
method by inheriting from ShowPrint
.
The code for Int
is shown below:
Examples:
0).show()
Int(2.0).show()
Float('Hello').show() Str(
0
2.0
Hello
Collection functions
Functions that manipulate popular python collections.
partition
partition (coll, f)
Partition a collection by a predicate
= partition(range(10), mod(2))
ts,fs 0,2,4,6,8])
test_eq(fs, [1,3,5,7,9]) test_eq(ts, [
flatten
flatten (o)
Concatenate all collections and items as a generator
concat
concat (colls)
Concatenate all collections and items as a list
for o in range(2)),[2,3,4], 5]) concat([(o
[0, 1, 2, 3, 4, 5]
"abc", "xyz"], ["foo", "bar"]]) concat([[
['abc', 'xyz', 'foo', 'bar']
strcat
strcat (its, sep:str='')
Concatenate stringified items its
'a',2]), 'a2')
test_eq(strcat(['a',2], ';'), 'a;2') test_eq(strcat([
detuplify
detuplify (x)
If x
is a tuple with one thing, extract it
None)
test_eq(detuplify(()),1]),1)
test_eq(detuplify([1,2]), [1,2])
test_eq(detuplify([1,2]])), np.array([[1,2]])) test_eq(detuplify(np.array([[
replicate
replicate (item, match)
Create tuple of item
copied len(match)
times
= [1,1]
t 1,2], t),([1,2],[1,2]))
test_eq(replicate([1, t),(1,1)) test_eq(replicate(
setify
setify (o)
Turn any list like-object into a set.
# test
None),set())
test_eq(setify('abc'),{'abc'})
test_eq(setify(1,2,2]),{1,2})
test_eq(setify([range(0,3)),{0,1,2})
test_eq(setify(1,2}),{1,2}) test_eq(setify({
merge
merge (*ds)
Merge all dictionaries in ds
test_eq(merge(), {})dict(a=1,b=2)), dict(a=1,b=2))
test_eq(merge(dict(a=1,b=2), dict(b=3,c=4), None), dict(a=1, b=3, c=4)) test_eq(merge(
range_of
range_of (x)
All indices of collection x
(i.e. list(range(len(x)))
)
1,1,1,1]), [0,1,2,3]) test_eq(range_of([
groupby
groupby (x, key, val=<function noop>)
Like itertools.groupby
but doesn’t need to be sorted, and isn’t lazy, plus some extensions
'aa ab bb'.split(), itemgetter(0)), {'a':['aa','ab'], 'b':['bb']}) test_eq(groupby(
Here’s an example of how to invert a grouping, using an int
as key
(which uses itemgetter
; passing a str
will use attrgetter
), and using a val
function:
= {0: [1, 3, 7], 2: [3], 3: [5], 4: [8], 5: [4], 7: [5]}
d for k,v in d.items() for o in v), 0, 1) groupby(((o,k)
{1: [0], 3: [0, 2], 7: [0], 5: [3, 7], 8: [4], 4: [5]}
last_index
last_index (x, o)
Finds the last index of occurence of x
in o
(returns -1 if no occurence)
9, [1, 2, 9, 3, 4, 9, 10]), 5)
test_eq(last_index(6, [1, 2, 9, 3, 4, 9, 10]), -1) test_eq(last_index(
filter_dict
filter_dict (d, func)
Filter a dict
using func
, applied to keys and values
= {o:chr(o) for o in range(65,73)}
letters letters
{65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H'}
lambda k,v: k<67 or v in 'FG') filter_dict(letters,
{65: 'A', 66: 'B', 70: 'F', 71: 'G'}
filter_keys
filter_keys (d, func)
Filter a dict
using func
, applied to keys
67)) filter_keys(letters, lt(
{65: 'A', 66: 'B'}
filter_values
filter_values (d, func)
Filter a dict
using func
, applied to values
'FG')) filter_values(letters, in_(
{70: 'F', 71: 'G'}
cycle
cycle (o)
Like itertools.cycle
except creates list of None
s if o
is empty
1,2,3]),5), [1,2,3,1,2])
test_eq(itertools.islice(cycle([3), [None]*3)
test_eq(itertools.islice(cycle([]),None),3), [None]*3)
test_eq(itertools.islice(cycle(1),3), [1,1,1]) test_eq(itertools.islice(cycle(
zip_cycle
zip_cycle (x, *args)
Like itertools.zip_longest
but cycle
s through elements of all but first argument
1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')]) test_eq(zip_cycle([
sorted_ex
sorted_ex (iterable, key=None, reverse=False)
Like sorted
, but if key is str use attrgetter
; if int use itemgetter
not_
not_ (f)
Create new function that negates result of f
def f(a): return a>0
1),True)
test_eq(f(1),False)
test_eq(not_(f)(=-1),True) test_eq(not_(f)(a
argwhere
argwhere (iterable, f, negate=False, **kwargs)
Like filter_ex
, but return indices for matching items
filter_ex
filter_ex (iterable, f=<function noop>, negate=False, gen=False, **kwargs)
Like filter
, but passing kwargs
to f
, defaulting f
to noop
, and adding negate
and gen
range_of
range_of (a, b=None, step=None)
All indices of collection a
, if a
is a collection, otherwise range
1,1,1,1]), [0,1,2,3])
test_eq(range_of([4), [0,1,2,3]) test_eq(range_of(
renumerate
renumerate (iterable, start=0)
Same as enumerate
, but returns index as 2nd element instead of 1st
'abc'), (('a',0),('b',1),('c',2))) test_eq(renumerate(
first
first (x, f=None, negate=False, **kwargs)
First element of x
, optionally filtered by f
, or None if missing
'a', 'b', 'c', 'd', 'e']), 'a')
test_eq(first([False]), False)
test_eq(first([False], noop), None) test_eq(first([
only
only (o)
Return the only item of o
, raise if o
doesn’t have exactly one item
nested_attr
nested_attr (o, attr, default=None)
Same as getattr
, but if attr
includes a .
, then looks inside nested objects
class CustomIndexable:
def __init__(self): self.data = {'a':1,'b':'v','c':{'d':5}}
def __getitem__(self, key): return self.data[key]
= CustomIndexable()
custom_indexable 'a'),1)
test_eq(nested_attr(custom_indexable,'c.d'),5)
test_eq(nested_attr(custom_indexable,'e'),None) test_eq(nested_attr(custom_indexable,
class TestObj: def init(self): self.nested = {‘key’: [1, 2, {‘inner’: ‘value’}]} test_obj = TestObj()
test_eq(nested_attr(test_obj, ‘nested.key.2.inner’),‘value’) test_eq(nested_attr([1, 2, 3], ‘1’),2)
= {'a':1,'b':'v','c':{'d':5}}
b 'b'),'v')
test_eq(nested_attr(b,'c.d'),5) test_eq(nested_attr(b,
= SimpleNamespace(b=(SimpleNamespace(c=1)))
a 'b.c'), getattr(getattr(a, 'b'), 'c'))
test_eq(nested_attr(a, 'b.d'), None)
test_eq(nested_attr(a, 'a'), 1) test_eq(nested_attr(b,
nested_setdefault
nested_setdefault (o, attr, default)
Same as setdefault
, but if attr
includes a .
, then looks inside nested objects
nested_callable
nested_callable (o, attr)
Same as nested_attr
but if not found will return noop
= SimpleNamespace(b=(SimpleNamespace(c=1)))
a 'b.c'), getattr(getattr(a, 'b'), 'c'))
test_eq(nested_callable(a, 'b.d'), noop) test_eq(nested_callable(a,
nested_idx
nested_idx (coll, *idxs)
Index into nested collections, dicts, etc, with idxs
= {'b':[1,{'c':2}]}
a 'nope'), None)
test_eq(nested_idx(a, 'nope', 'nup'), None)
test_eq(nested_idx(a, 'b', 3), None)
test_eq(nested_idx(a,
test_eq(nested_idx(a), a)'b'), [1,{'c':2}])
test_eq(nested_idx(a, 'b', 1), {'c':2})
test_eq(nested_idx(a, 'b', 1, 'c'), 2) test_eq(nested_idx(a,
= SimpleNamespace(b=[1,{'c':2}])
a 'nope'), None)
test_eq(nested_idx(a, 'nope', 'nup'), None)
test_eq(nested_idx(a, 'b', 3), None)
test_eq(nested_idx(a,
test_eq(nested_idx(a), a)'b'), [1,{'c':2}])
test_eq(nested_idx(a, 'b', 1), {'c':2})
test_eq(nested_idx(a, 'b', 1, 'c'), 2) test_eq(nested_idx(a,
set_nested_idx
set_nested_idx (coll, value, *idxs)
Set value indexed like `nested_idx
3, 'b', 0)
set_nested_idx(a, 'b', 0), 3) test_eq(nested_idx(a,
val2idx
val2idx (x)
Dict from value to index
1,2,3]), {3:2,1:0,2:1}) test_eq(val2idx([
uniqueify
uniqueify (x, sort=False, bidir=False, start=None)
Unique elements in x
, optional sort
, optional return reverse correspondence, optional prepend with elements.
= [1,1,0,5,0,3]
t 1,0,5,3])
test_eq(uniqueify(t),[=True),[0,1,3,5])
test_eq(uniqueify(t, sort=[7,8,6]), [7,8,6,1,0,5,3])
test_eq(uniqueify(t, start= uniqueify(t, bidir=True)
v,o 1,0,5,3])
test_eq(v,[1:0, 0: 1, 5: 2, 3: 3})
test_eq(o,{= uniqueify(t, sort=True, bidir=True)
v,o 0,1,3,5])
test_eq(v,[0:0, 1: 1, 3: 2, 5: 3}) test_eq(o,{
loop_first_last
loop_first_last (values)
Iterate and generate a tuple with a flag for first and last value.
range(3)), [(True,False,0), (False,False,1), (False,True,2)]) test_eq(loop_first_last(
loop_first
loop_first (values)
Iterate and generate a tuple with a flag for first value.
range(3)), [(True,0), (False,1), (False,2)]) test_eq(loop_first(
loop_last
loop_last (values)
Iterate and generate a tuple with a flag for last value.
range(3)), [(False,0), (False,1), (True,2)]) test_eq(loop_last(
first_match
first_match (lst, f, default=None)
First element of lst
matching predicate f
, or default
if none
= [0,2,4,5,6,7,10]
a lambda o:o%2), 3) test_eq(first_match(a,
last_match
last_match (lst, f, default=None)
Last element of lst
matching predicate f
, or default
if none
lambda o:o%2), 5) test_eq(last_match(a,
fastuple
A tuple with extended functionality.
fastuple
fastuple (x=None, *rest)
A tuple
with elementwise ops and more friendly init behavior
Friendly init behavior
Common failure modes when trying to initialize a tuple in python:
tuple(3)
> TypeError: 'int' object is not iterable
or
tuple(3, 4)
> TypeError: tuple expected at most 1 arguments, got 2
However, fastuple
allows you to define tuples like this and in the usual way:
3), (3,))
test_eq(fastuple(3,4), (3, 4))
test_eq(fastuple(3,4)), (3, 4)) test_eq(fastuple((
Elementwise operations
fastuple.add
fastuple.add (*args)
+
is already defined in tuple
for concat, so use add
instead
1,1),(2,2)), (3,3))
test_eq(fastuple.add((1,1).add(2), fastuple(3,3))
test_eq_type(fastuple('1','2').add('2'), fastuple('12','22')) test_eq(fastuple(
fastuple.mul
fastuple.mul (*args)
*
is already defined in tuple
for replicating, so use mul
instead
1,1).mul(2), fastuple(2,2)) test_eq_type(fastuple(
Other Elementwise Operations
Additionally, the following elementwise operations are available: - le
: less than or equal - eq
: equal - gt
: greater than - min
: minimum of
3,1).le(1), (False, True))
test_eq(fastuple(3,1).eq(1), (False, True))
test_eq(fastuple(3,1).gt(1), (True, False))
test_eq(fastuple(3,1).min(2), (2,1)) test_eq(fastuple(
You can also do other elementwise operations like negate a fastuple
, or subtract two fastuple
s:
-fastuple(1,2), (-1,-2))
test_eq(~fastuple(1,0,1), (False,True,False))
test_eq(
1,1)-fastuple(2,2), (-1,-1)) test_eq(fastuple(
type(fastuple(1)), fastuple)
test_eq(1,2), fastuple(1,2))
test_eq_type(fastuple(1,2), fastuple(1,3))
test_ne(fastuple( test_eq(fastuple(), ())
Functions on Functions
Utilities for functional programming or for defining, modifying, or debugging functions.
bind
bind (func, *pargs, **pkwargs)
Same as partial
, except you can use arg0
arg1
etc param placeholders
bind
is the same as partial
, but also allows you to reorder positional arguments using variable name(s) arg{i}
where i refers to the zero-indexed positional argument. bind
as implemented currently only supports reordering of up to the first 5 positional arguments.
Consider the function myfunc
below, which has 3 positional arguments. These arguments can be referenced as arg0
, arg1
, and arg1
, respectively.
def myfn(a,b,c,d=1,e=2): return(a,b,c,d,e)
In the below example we bind the positional arguments of myfn
as follows:
- The second input
14
, referenced byarg1
, is substituted for the first positional argument. - We supply a default value of
17
for the second positional argument. - The first input
19
, referenced byarg0
, is subsituted for the third positional argument.
17, arg0, e=3)(19,14), (14,17,19,1,3)) test_eq(bind(myfn, arg1,
In this next example:
- We set the default value to
17
for the first positional argument. - The first input
19
refrenced byarg0
, becomes the second positional argument. - The second input
14
becomes the third positional argument. - We override the default the value for named argument
e
to3
.
17, arg0, e=3)(19,14), (17,19,14,1,3)) test_eq(bind(myfn,
This is an example of using bind
like partial
and do not reorder any arguments:
17,19,14), (17,19,14,1,2)) test_eq(bind(myfn)(
bind
can also be used to change default values. In the below example, we use the first input 3
to override the default value of the named argument e
, and supply default values for the first three positional arguments:
17,19,14,e=arg0)(3), (17,19,14,1,3)) test_eq(bind(myfn,
mapt
mapt (func, *iterables)
Tuplified map
= [0,1,2,3]
t 0,-1,-2,-3)) test_eq(mapt(operator.neg, t), (
map_ex
map_ex (iterable, f, *args, gen=False, **kwargs)
Like map
, but use bind
, and supports str
and indexing
0,-1,-2,-3]) test_eq(map_ex(t,operator.neg), [
If f
is a string then it is treated as a format string to create the mapping:
'#{}#'), ['#0#','#1#','#2#','#3#']) test_eq(map_ex(t,
If f
is a dictionary (or anything supporting __getitem__
) then it is indexed to create the mapping:
list('abcd')), list('abcd')) test_eq(map_ex(t,
You can also pass the same arg
params that bind
accepts:
def f(a=None,b=None): return b
=arg0), range(4)) test_eq(map_ex(t, f, b
compose
compose (*funcs, order=None)
Create a function that composes all functions in funcs
, passing along remaining *args
and **kwargs
to all
= lambda o,p=0: (o*2)+p
f1 = lambda o,p=1: (o+1)/p
f2 3)), compose(f1,f2)(3))
test_eq(f2(f1(3,p=3),p=3), compose(f1,f2)(3,p=3))
test_eq(f2(f1(3, 3), 3), compose(f1,f2)(3, 3))
test_eq(f2(f1(
= 1
f1.order 3)), compose(f1,f2, order="order")(3)) test_eq(f1(f2(
maps
maps (*args, retain=<function noop>)
Like map
, except funcs are composed first
1]), [1])
test_eq(maps([1,2]), [-1,-2])
test_eq(maps(operator.neg, [1,2]), [1,2]) test_eq(maps(operator.neg, operator.neg, [
partialler
partialler (f, *args, order=None, **kwargs)
Like functools.partial
but also copies over docstring
def _f(x,a=1):
"test func"
return x-a
=1
_f.order
= partialler(_f, 2)
f 1)
test_eq(f.order, 3), -1)
test_eq(f(= partialler(_f, a=2, order=3)
f "test func")
test_eq(f.__doc__, 3)
test_eq(f.order, 3), _f(3,2)) test_eq(f(
class partial0:
"Like `partialler`, but args passed to callable are inserted at started, instead of at end"
def __init__(self, f, *args, order=None, **kwargs):
self.f,self.args,self.kwargs = f,args,kwargs
self.order = ifnone(order, getattr(f,'order',None))
self.__doc__ = f.__doc__
def __call__(self, *args, **kwargs): return self.f(*args, *self.args, **kwargs, **self.kwargs)
= partial0(_f, 2)
f 1)
test_eq(f.order, 3), 1) # NB: different to `partialler` example test_eq(f(
instantiate
instantiate (t)
Instantiate t
if it’s a type, otherwise do nothing
int), 0)
test_eq_type(instantiate(1), 1) test_eq_type(instantiate(
using_attr
using_attr (f, attr)
Construct a function which applies f
to the argument’s attribute attr
= Path('/a/b.txt')
t = using_attr(str.upper, 'name')
f 'B.TXT') test_eq(f(t),
Self (with an uppercase S)
A Concise Way To Create Lambdas
This is a concise way to create lambdas that are calling methods on an object (note the capitalization!)
Self.sum()
, for instance, is a shortcut for lambda o: o.sum()
.
= Self.sum()
f = np.array([3.,1])
x 4.)
test_eq(f(x),
# This is equivalent to above
= lambda o: o.sum()
f = np.array([3.,1])
x 4.)
test_eq(f(x),
= Self.argmin()
f = np.array([1,2,3,4,5])
arr
test_eq(f(arr), arr.argmin())
= Self.sum().is_integer()
f = np.array([3.,1])
x True)
test_eq(f(x),
= Self.sum().real.is_integer()
f = np.array([3.,1])
x True)
test_eq(f(x),
= Self.imag()
f 3), 0)
test_eq(f(
= Self[1]
f 1) test_eq(f(x),
Self
is also callable, which creates a function which calls any function passed to it, using the arguments passed to Self
:
def f(a, b=3): return a+b+2
def g(a, b=3): return a*b
= Self(1,b=2)
fg list(map(fg, [f,g]))
[5, 2]
Patching
copy_func
copy_func (f)
Copy a non-builtin function (NB copy.copy
does not work for this)
Sometimes it may be desirable to make a copy of a function that doesn’t point to the original object. When you use Python’s built in copy.copy
or copy.deepcopy
to copy a function, you get a reference to the original object:
import copy as cp
def foo(): pass
= cp.copy(foo)
a = cp.deepcopy(foo)
b
= 'hello' # since a and b point at the same object, updating a will update b
a.someattr 'hello')
test_eq(b.someattr,
assert a is foo and b is foo
However, with copy_func
, you can retrieve a copy of a function without a reference to the original object:
= copy_func(foo) # c is an indpendent object
c assert c is not foo
def g(x, *, y=3): return x+y
4), 7) test_eq(copy_func(g)(
patch_to
patch_to (cls, as_prop=False, cls_method=False)
Decorator: add f
to cls
The @patch_to
decorator allows you to monkey patch a function into a class as a method:
class _T3(int): pass
@patch_to(_T3)
def func1(self, a): return self+a
= _T3(1) # we initialized `t` to a type int = 1
t 2), 3) # we add 2 to `t`, so 2 + 1 = 3 test_eq(t.func1(
You can access instance properties in the usual way via self
:
class _T4():
def __init__(self, g): self.g = g
@patch_to(_T4)
def greet(self, x): return self.g + x
= _T4('hello ') # this sets self.g = 'hello '
t 'world'), 'hello world') #t.greet('world') will append 'world' to 'hello ' test_eq(t.greet(
You can instead specify that the method should be a class method by setting cls_method=True
:
class _T5(int): attr = 3 # attr is a class attribute we will access in a later method
@patch_to(_T5, cls_method=True)
def func(cls, x): return cls.attr + x # you can access class attributes in the normal way
4), 7) test_eq(_T5.func(
Additionally you can specify that the function you want to patch should be a class attribute with as_prop=True
:
@patch_to(_T5, as_prop=True)
def add_ten(self): return self + 10
= _T5(4)
t 14) test_eq(t.add_ten,
Instead of passing one class to the @patch_to
decorator, you can pass multiple classes in a tuple to simulteanously patch more than one class with the same method:
class _T6(int): pass
class _T7(int): pass
@patch_to((_T6,_T7))
def func_mult(self, a): return self*a
= _T6(2)
t 4), 8)
test_eq(t.func_mult(= _T7(2)
t 4), 8) test_eq(t.func_mult(
patch
patch (f=None, as_prop=False, cls_method=False)
Decorator: add f
to the first parameter’s class (based on f’s type annotations)
@patch
is an alternative to @patch_to
that allows you similarly monkey patch class(es) by using type annotations:
class _T8(int): pass
@patch
def func(self:_T8, a): return self+a
= _T8(1) # we initilized `t` to a type int = 1
t 3), 4) # we add 3 to `t`, so 3 + 1 = 4
test_eq(t.func(__qualname__, '_T8.func') test_eq(t.func.
Similarly to patch_to
, you can supply a union of classes instead of a single class in your type annotations to patch multiple classes:
class _T9(int): pass
@patch
def func2(x:_T8|_T9, a): return x*a # will patch both _T8 and _T9
= _T8(2)
t 4), 8)
test_eq(t.func2(__qualname__, '_T8.func2')
test_eq(t.func2.
= _T9(2)
t 4), 8)
test_eq(t.func2(__qualname__, '_T9.func2') test_eq(t.func2.
Just like patch_to
decorator you can use as_prop
and cls_method
parameters with patch
decorator:
@patch(as_prop=True)
def add_ten(self:_T5): return self + 10
= _T5(4)
t 14) test_eq(t.add_ten,
class _T5(int): attr = 3 # attr is a class attribute we will access in a later method
@patch(cls_method=True)
def func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way
4), 7) test_eq(_T5.func(
patch_property
patch_property (f)
Deprecated; use patch(as_prop=True)
instead
Patching classmethod
shouldn’t affect how python’s inheritance works
class FastParent: pass
@patch(cls_method=True)
def type_cls(cls: FastParent): return cls
class FastChild(FastParent): pass
= FastParent()
parent
test_eq(parent.type_cls(), FastParent)
= FastChild()
child test_eq(child.type_cls(), FastChild)
Other Helpers
compile_re
compile_re (pat)
Compile pat
if it’s not None
assert compile_re(None) is None
assert compile_re('a').match('ab')
ImportEnum
ImportEnum (value, names=None, module=None, qualname=None, type=None, start=1)
An Enum
that can have its values imported
= ImportEnum('_T', {'foobar':1, 'goobar':2})
_T
_T.imports()
test_eq(foobar, _T.foobar) test_eq(goobar, _T.goobar)
StrEnum
StrEnum (value, names=None, module=None, qualname=None, type=None, start=1)
An ImportEnum
that behaves like a str
str_enum
str_enum (name, *vals)
Simplified creation of StrEnum
types
ValEnum
ValEnum (value, names=None, module=None, qualname=None, type=None, start=1)
An ImportEnum
that stringifies using values
= str_enum('_T', 'a', 'b')
_T f'{_T.a}', 'a')
test_eq('a')
test_eq(_T.a, list(_T.__members__), ['a','b'])
test_eq(print(_T.a, _T.a.upper())
a A
Stateful
Stateful (*args, **kwargs)
A base class/mixin for objects that should not serialize all their state
class _T(Stateful):
def __init__(self):
super().__init__()
self.a=1
self._state['test']=2
= _T()
t = pickle.loads(pickle.dumps(t))
t2 1)
test_eq(t.a,'test'],2)
test_eq(t._state[1)
test_eq(t2.a, test_eq(t2._state,{})
Override _init_state
to do any necessary setup steps that are required during __init__
or during deserialization (e.g. pickle.load
). Here’s an example of how Stateful
simplifies the official Python example for Handling Stateful Objects.
class TextReader(Stateful):
"""Print and number lines in a text file."""
=('file',)
_stateattrsdef __init__(self, filename):
self.filename,self.lineno = filename,0
super().__init__()
def readline(self):
self.lineno += 1
= self.file.readline()
line if line: return f"{self.lineno}: {line.strip()}"
def _init_state(self):
self.file = open(self.filename)
for _ in range(self.lineno): self.file.readline()
= TextReader("00_test.ipynb")
reader print(reader.readline())
print(reader.readline())
= pickle.loads(pickle.dumps(reader))
new_reader print(reader.readline())
1: {
2: "cells": [
3: {
NotStr
NotStr (s)
Behaves like a str
, but isn’t an instance of one
= NotStr("hello")
s assert not isinstance(s, str)
'hello')
test_eq(s, *2, 'hellohello')
test_eq(slen(s), 5) test_eq(
PrettyString
Little hack to get strings to show properly in Jupyter.
Allow strings with special characters to render properly in Jupyter. Without calling print()
strings with special characters are displayed like so:
='a string\nwith\nnew\nlines and\ttabs'
with_special_chars with_special_chars
'a string\nwith\nnew\nlines and\ttabs'
We can correct this with PrettyString
:
PrettyString(with_special_chars)
a string
with
new
lines and tabs
even_mults
even_mults (start, stop, n)
Build log-stepped array from start
to stop
in n
steps.
2,8,3), [2,4,8])
test_eq(even_mults(2,32,5), [2,4,8,16,32])
test_eq(even_mults(2,8,1), 8) test_eq(even_mults(
num_cpus
num_cpus ()
Get number of cpus
num_cpus()
10
add_props
add_props (f, g=None, n=2)
Create properties passing each of range(n)
to f
class _T(): a,b = add_props(lambda i,x:i*2)
= _T()
t 0)
test_eq(t.a,2) test_eq(t.b,
class _T():
def __init__(self, v): self.v=v
def _set(i, self, v): self.v[i] = v
= add_props(lambda i,x: x.v[i], _set)
a,b
= _T([0,2])
t 0)
test_eq(t.a,2)
test_eq(t.b,= t.a+1
t.a = 3
t.b 1)
test_eq(t.a,3) test_eq(t.b,
str2bool
str2bool (s)
Case-insensitive convert string s
too a bool (y
,yes
,t
,true
,on
,1
->True
)
True values are ‘y’, ‘yes’, ‘t’, ‘true’, ‘on’, and ‘1’; false values are ‘n’, ‘no’, ‘f’, ‘false’, ‘off’, and ‘0’. Raises ValueError
if ‘val’ is anything else.
for o in "y YES t True on 1".split(): assert str2bool(o)
for o in "n no FALSE off 0".split(): assert not str2bool(o)
for o in 0,None,'',False: assert not str2bool(o)
for o in 1,True: assert str2bool(o)
str2int
str2int (s)
Convert s
to an int
str2float
str2float (s:str)
Convert s
to a float
str2list
str2list (s:str)
Convert s
to a list
str2date
str2date (s:str)
date.fromisoformat
with empty string handling
to_date
to_date (arg)
to_list
to_list (arg)
to_float
to_float (arg)
to_int
to_int (arg)
to_bool
to_bool (arg)
typed
typed (_func=None, cast=False)
Decorator to check param and return types at runtime, with optional casting
typed
validates argument types at runtime. This is in contrast to MyPy which only offers static type checking.
For example, a TypeError
will be raised if we try to pass an integer into the first argument of the below function:
@typed
def discount(price:int, pct:float) -> float:
return (1-pct) * price
with ExceptionExpected(TypeError): discount(100.0, .1)
You can have automatic casting based on heuristics by specifying typed(cast=True)
. If casting is not possible, a TypeError
is raised.
@typed(cast=True)
def discount(price:int, pct:float) -> float:
return (1-pct) * price
assert 90.0 == discount(100.5, .1) # will auto cast 100.5 to the int 100
assert 90.0 == discount(' 100 ', .1) # will auto cast the str "100" to the int 100
with ExceptionExpected(TypeError): discount("a", .1)
We can also optionally allow multiple types by enumarating the types in a tuple as illustrated below:
@typed
def discount(price:int|float, pct:float):
return (1-pct) * price
assert 90.0 == discount(100.0, .1)
@typed(cast=True)
def discount(price:int|None, pct:float):
return (1-pct) * price
assert 90.0 == discount(100.0, .1)
We currently do not support union types when casting.
@typed(cast=True)
def discount(price:int|float, pct:float):
return (1-pct) * price
with ExceptionExpected(AssertionError): assert 90.0 == discount("100.0", .1)
typed
works with classes, too:
class Foo:
@typed
def __init__(self, a:int, b: int, c:str): pass
@typed(cast=True)
def test(cls, d:str): return d
with ExceptionExpected(TypeError): Foo(1, 2, 3)
assert isinstance(Foo(1,2, 'a string').test(10), str)
It also works with custom types.
@typed
def test_foo(foo: Foo): pass
with ExceptionExpected(TypeError): test_foo(1)
1, 2, 'a string')) test_foo(Foo(
class Bar:
@typed
def __init__(self, a:int): self.a = a
@typed(cast=True)
def test_bar(bar: Bar): return bar
assert isinstance(test_bar(1), Bar)
1).a, 1)
test_eq(test_bar(with ExceptionExpected(TypeError): test_bar("foobar")
exec_new
exec_new (code)
Execute code
in a new environment and return it
= exec_new('a=1')
g 'a'], 1) test_eq(g[
exec_import
exec_import (mod, sym)
Import sym
from mod
in a new environment
Notebook functions
ipython_shell
ipython_shell ()
Same as get_ipython
but returns False
if not in IPython
in_ipython
in_ipython ()
Check if code is running in some kind of IPython environment
in_colab
in_colab ()
Check if the code is running in Google Colaboratory
in_jupyter
in_jupyter ()
Check if the code is running in a jupyter notebook
in_notebook
in_notebook ()
Check if the code is running in a jupyter notebook
These variables are available as booleans in fastcore.basics
as IN_IPYTHON
, IN_JUPYTER
, IN_COLAB
and IN_NOTEBOOK
.
IN_IPYTHON, IN_JUPYTER, IN_COLAB, IN_NOTEBOOK
(True, True, False, True)