from fastcore.test import *
from nbdev.showdoc import *
from fastcore.nb_imports import *
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).
test_eq(ifnone(None,1), 1)
test_eq(ifnone(2 ,1), 2)
Return the attribute attr
for object o
. If the attribute doesn't exist, then return the object o
instead.
class myobj: myattr='foo'
test_eq(maybe_attr(myobj, 'myattr'), 'foo')
test_eq(maybe_attr(myobj, 'another_attr'), myobj)
Lookup a user-supplied list of attributes (flds
) of an object and 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:
a=1
b='foo'
__repr__=basic_repr('a,b')
__name__='some-class'
class AnotherClass:
c=SomeClass()
d='bar'
__repr__=basic_repr(['c', 'd'])
sc = SomeClass()
ac = AnotherClass()
test_eq(repr(sc), "SomeClass(a=1, b='foo')")
test_eq(repr(ac), "AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')")
is_array(np.array(1)),is_array([1])
Conversion is designed to "do what you mean", e.g:
test_eq(listify('hi'), ['hi'])
test_eq(listify(array(1)), [array(1)])
test_eq(listify(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])
arr = np.arange(9).reshape(3,3)
listify(arr)
listify(array([1,2]))
Generators are turned into lists too:
gen = (o for o in range(3))
test_eq(listify(gen), [0,1,2])
Use match
to provide a length to match:
test_eq(listify(1,match=3), [1,1,1])
If match
is a sequence, it's length is used:
test_eq(listify(1,match=range(3)), [1,1,1])
If the listified item is not of length 1
, it must be the same length as match
:
test_eq(listify([1,1,1],match=3), [1,1,1])
test_fail(lambda: listify([1,1],match=3))
test_eq(tuplify(None),())
test_eq(tuplify([1,2,3]),(1,2,3))
test_eq(tuplify(1,match=[1,2,3]),(1,1,1))
[(o,true(o)) for o in
(array(0),array(1),array([0]),array([0,1]),1,0,'',None)]
bool(null.hi().there[3])
bool(tonull(None).hi().there[3])
_t = get_class('_t', 'a', b=2)
t = _t()
test_eq(t.a, None)
test_eq(t.b, 2)
t = _t(1, b=3)
test_eq(t.a, 1)
test_eq(t.b, 3)
t = _t(1, 3)
test_eq(t.a, 1)
test_eq(t.b, 3)
test_eq(repr(t), '_t(a=1, b=3)')
test_eq(t, pickle.loads(pickle.dumps(t)))
Any kwargs
will be added as class attributes, and sup
is an optional (tuple of) base classes.
mk_class('_t', a=1, sup=dict)
t = _t()
test_eq(t.a, 1)
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
mk_class('_t', 'a', sup=dict, doc='test doc', funcs=foo)
t = _t(3, b=2)
test_eq(t.a, 3)
test_eq(t.b, 2)
test_eq(t.foo(), 1)
test_eq(t.__doc__, 'test doc')
t
@wrap_class('_t', a=2)
def bar(self,x): return x+1
t = _t()
test_eq(t.a, 2)
test_eq(t.bar(3), 4)
with ignore_exceptions():
# Exception will be ignored
raise Exception
test_eq(exec_local("a=1", "a"), 1)
assert risinstance(int, 1)
assert not risinstance(str, 0)
assert risinstance(int)(1)
types
can also be strings:
assert risinstance(('str','int'), 'a')
assert risinstance('str', 'a')
assert not risinstance('int', 'a')
noop()
test_eq(noop(1),1)
class _t: foo=noops
test_eq(_t().foo(1),1)
Inf
defines the following properties:
count: itertools.count()
zeros: itertools.cycle([0])
ones : itertools.cycle([1])
nones: itertools.cycle([None])
test_eq([o for i,o in zip(range(5), Inf.count)],
[0, 1, 2, 3, 4])
test_eq([o for i,o in zip(range(5), Inf.zeros)],
[0]*5)
test_eq([o for i,o in zip(range(5), Inf.ones)],
[1]*5)
test_eq([o for i,o in zip(range(5), Inf.nones)],
[None]*5)
assert in_('c', ('b', 'c', 'a'))
assert in_(4, [2,3,4,5])
assert in_('t', 'fastai')
test_fail(in_('h', 'fastai'))
# use in_ as a partial
assert in_('fastai')('t')
assert in_([2,3,4,5])(4)
test_fail(in_('fastai')('h'))
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.
lt(3,5),gt(3,5),is_(None,None),in_(0,[1,2])
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.
lt(5)(3),gt(5)(3),is_(None)(None),in_([1,2])(0)
assert true(1,2,3)
assert true(False)
assert true(None)
assert true([])
test_eq(gen(noop, Inf.count, lt(5)),
range(5))
test_eq(gen(operator.neg, Inf.count, gt(-5)),
[0,-1,-2,-3,-4])
test_eq(gen(lambda o:o if o<5 else stop(), Inf.count),
range(5))
Note that you must pass either chunk_sz
, or n_chunks
, but not both.
t = list(range(10))
test_eq(chunked(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], ])
t = map(lambda o:stop() if o==6 else o, Inf.count)
test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5]])
t = map(lambda o:stop() if o==7 else o, Inf.count)
test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5], [6]])
t = np.arange(10)
test_eq(chunked(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(otherwise(2+1, gt(3), 4), 3)
test_eq(otherwise(2+1, gt(2), 4), 4)
These functions reduce boilerplate when setting or manipulating attributes or properties of objects.
custom_dir
allows you extract the __dict__
property of a class and appends the list add
to it.
class _T:
def f(): pass
s = custom_dir(_T(), add=['foo', 'bar'])
assert {'foo', 'bar', 'f'}.issubset(s)
d = AttrDict(a=1,b="two")
test_eq(d.a, 1)
test_eq(d['b'], 'two')
test_eq(d.get('c','nope'), 'nope')
d.b = 2
test_eq(d.b, 2)
test_eq(d['b'], 2)
d['b'] = 3
test_eq(d['b'], 3)
test_eq(d.b, 3)
assert 'a' in dir(d)
def type_hints(f):
"Same as `typing.get_type_hints` but returns `{}` if not allowed type"
return typing.get_type_hints(f) if isinstance(f, typing._allowed_types) else {}
Below is a list of allowed types for type hints in python:
list(typing._allowed_types)
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)
exp = {'a':int,'return':bool}
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)
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)
def f(x) -> float: return x
test_eq(anno_ret(f), float)
def f(x) -> typing.Tuple[float,float]: return x
test_eq(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
test_eq(anno_ret(f), None)
test_eq(anno_ret(None), None) # instead of passing in a func, pass in None
test_eq(argnames(f), ['x'])
@with_cast
def _f(a, b:Path, c:str='', d=0): return (a,b,c,d)
test_eq(_f(1, '.', 3), (1,Path('.'),'3',0))
test_eq(_f(1, '.'), (1,Path('.'),'',0))
@with_cast
def _g(a:int=0)->str: return a
test_eq(_g(4.0), '4')
test_eq(_g(4.4), '4')
test_eq(_g(2), '2')
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 = T(1,c=2,b=3)
assert t.a==1 and t.b==3 and t.c==2
In addition, it stores the attrs as a dict
in __stored_args__
, which you can use for display, logging, and so forth.
test_eq(t.__stored_args__, {'a':1, 'b':3, 'c':2})
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 = T(1,c=2,b=3)
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 = T(1,c=2,b=3)
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)
store_attr('d')
t = T2(d=1,a=2,b=3,c=4)
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 = T(1,c=2,b=3)
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()
t = T4(4, a=1,c=2,b=3)
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()
t = T4(a=3)
assert t.a==3 and t.b==1
t = T4(a=3, b=2)
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 = T(1,c=2,b=3)
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):
t = SimpleNamespace()
store_attr(self=t)
return t
t = create_T(a=1, b=2)
assert t.a==1 and t.b==2
class T:
def __init__(self, a,b,c): store_attr()
t = T(1,c=2,b=3)
test_eq(attrdict(t,'b','c'), {'b':3, 'c':2})
class T:
def a(self): return 1
def b(self): return 2
properties(T,'a')
test_eq(T().a,1)
test_eq(T().b(),2)
test_eq(camel2words('ClassAreCamel'), 'Class Are Camel')
test_eq(camel2snake('ClassAreCamel'), 'class_are_camel')
test_eq(camel2snake('Already_Snake'), 'already__snake')
test_eq(snake2camel('a_b_cc'), 'ABCc')
class Parent:
@property
def name(self): return class2attr(self, 'Parent')
class ChildOfParent(Parent): pass
class ParentChildOf(Parent): pass
p = Parent()
cp = ChildOfParent()
cp2 = ParentChildOf()
test_eq(p.name, 'parent')
test_eq(cp.name, 'child_of')
test_eq(cp2.name, 'parent_child_of')
from fractions import Fraction
getattrs(Fraction(1,2), 'numerator', 'denominator')
assert hasattrs(1,('imag','real'))
assert not hasattrs(1,('imag','foo'))
d = dict(a=1,bb="2",ignore=3)
o = SimpleNamespace()
setattrs(o, "a,bb", d)
test_eq(o.a, 1)
test_eq(o.bb, "2")
d = SimpleNamespace(a=1,bb="2",ignore=3)
o = SimpleNamespace()
setattrs(o, "a,bb", d)
test_eq(o.a, 1)
test_eq(o.bb, "2")
test_eq(try_attrs(1, 'real'), 1)
test_eq(try_attrs(1, 'foobar', 'real'), 1)
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 toself.default
_default
can be set to the name of any instance attribute that does not start with dunder__
- By default, this is set to
_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
- By default, this is
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
page = _WebPage('Soap', author="Sylvain")
p = _ProductPage(page, 15.0)
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.
p = _ProductPage(page, 15.0)
Now, we can access the author
attribute directly from the instance:
test_eq(p.author, 'Sylvain')
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):
_default = '_data' # use different component name; `self._data` rather than `self.default`
def __init__(self,a): self._data = a
def foo(self): noop
t = _C('Hi')
test_eq(t._data, 'Hi')
test_fail(lambda: t.default) # we no longer have self.default
test_eq(t.lower(), 'hi')
test_eq(t.upper(), 'HI')
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
t = _C('Hi')
test_eq(t.lower(), 'hi')
test_eq(t.upper(), 'HI')
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):
_xtra = ['lower'] # specify which attributes get passed to `self.default`
def __init__(self,a): self.default = a
def foo(self): noop
t = _C('Hi')
test_eq(t.default, 'Hi')
test_eq(t.lower(), 'hi')
test_fail(lambda: t.upper()) # upper wasn't in _xtra, so it isn't available to be called
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):
_default = 'data' # use a bad component name; i.e. self.data does not exist
def __init__(self,a): self.default = a
def foo(self): noop
# TODO: should we raise an error when we create a new instance ...
t = _C('Hi')
test_eq(t.default, 'Hi')
# ... or is it enough for all GetAttr features to raise errors
test_fail(lambda: t.data)
test_fail(lambda: t.lower())
test_fail(lambda: t.upper())
test_fail(lambda: dir(t))
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')
t = _C('HELLO') # delegates to a string
test_eq(t.lower(), 'hello')
t = _C(np.array([5,4,3])) # delegates to a numpy array
test_eq(t.sum(), 12)
t = _C(pd.DataFrame({'a': [1,2], 'b': [3,4]})) # delegates to a pandas.DataFrame
test_eq(t.b.max(), 4)
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.
Examples:
Int(0).show()
Float(2.0).show()
Str('Hello').show()
concat([(o for o in range(2)),[2,3,4]])
test_eq(strcat(['a',2]), 'a2')
test_eq(strcat(['a',2], ';'), 'a;2')
test_eq(detuplify(()),None)
test_eq(detuplify([1]),1)
test_eq(detuplify([1,2]), [1,2])
test_eq(detuplify(np.array([[1,2]])), np.array([[1,2]]))
t = [1,1]
test_eq(replicate([1,2], t),([1,2],[1,2]))
test_eq(replicate(1, t),(1,1))
test_eq(setify(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(merge(), {})
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(range_of([1,1,1,1]), [0,1,2,3])
test_eq(groupby('aa ab bb'.split(), itemgetter(0)), {'a':['aa','ab'], 'b':['bb']})
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:
d = {0: [1, 3, 7], 2: [3], 3: [5], 4: [8], 5: [4], 7: [5]}
groupby(((o,k) for k,v in d.items() for o in v), 0, 1)
test_eq(last_index(9, [1, 2, 9, 3, 4, 9, 10]), 5)
test_eq(last_index(6, [1, 2, 9, 3, 4, 9, 10]), -1)
letters = {o:chr(o) for o in range(65,73)}
letters
filter_dict(letters, lambda k,v: k<67 or v in 'FG')
filter_keys(letters, lt(67))
filter_values(letters, in_('FG'))
test_eq(itertools.islice(cycle([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(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])
def f(a): return a>0
test_eq(f(1),True)
test_eq(not_(f)(1),False)
test_eq(not_(f)(a=-1),True)
test_eq(range_of([1,1,1,1]), [0,1,2,3])
test_eq(range_of(4), [0,1,2,3])
test_eq(renumerate('abc'), (('a',0),('b',1),('c',2)))
test_eq(first(['a', 'b', 'c', 'd', 'e']), 'a')
test_eq(first([False]), False)
test_eq(first([False], noop), None)
a = SimpleNamespace(b=(SimpleNamespace(c=1)))
test_eq(nested_attr(a, 'b.c'), getattr(getattr(a, 'b'), 'c'))
test_eq(nested_attr(a, 'b.d'), None)
a = {'b':[1,{'c':2}]}
test_eq(nested_idx(a), a)
test_eq(nested_idx(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(val2idx([1,2,3]), {3:2,1:0,2:1})
t = [1,1,0,5,0,3]
test_eq(uniqueify(t),[1,0,5,3])
test_eq(uniqueify(t, sort=True),[0,1,3,5])
test_eq(uniqueify(t, start=[7,8,6]), [7,8,6,1,0,5,3])
v,o = uniqueify(t, bidir=True)
test_eq(v,[1,0,5,3])
test_eq(o,{1:0, 0: 1, 5: 2, 3: 3})
v,o = uniqueify(t, sort=True, bidir=True)
test_eq(v,[0,1,3,5])
test_eq(o,{0:0, 1: 1, 3: 2, 5: 3})
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:
test_eq(fastuple(3), (3,))
test_eq(fastuple(3,4), (3, 4))
test_eq(fastuple((3,4)), (3, 4))
test_eq(fastuple.add((1,1),(2,2)), (3,3))
test_eq_type(fastuple(1,1).add(2), fastuple(3,3))
test_eq(fastuple('1','2').add('2'), fastuple('12','22'))
test_eq_type(fastuple(1,1).mul(2), fastuple(2,2))
test_eq(fastuple(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(1,2), (-1,-2))
test_eq(~fastuple(1,0,1), (False,True,False))
test_eq(fastuple(1,1)-fastuple(2,2), (-1,-1))
test_eq(type(fastuple(1)), fastuple)
test_eq_type(fastuple(1,2), fastuple(1,2))
test_ne(fastuple(1,2), fastuple(1,3))
test_eq(fastuple(), ())
Utilities for functional programming or for defining, modifying, or debugging functions.
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:
test_eq(bind(myfn, arg1, 17, arg0, e=3)(19,14), (14,17,19,1,3))
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
.
test_eq(bind(myfn, 17, arg0, e=3)(19,14), (17,19,14,1,3))
This is an example of using bind
like partial
and do not reorder any arguments:
test_eq(bind(myfn)(17,19,14), (17,19,14,1,2))
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:
test_eq(bind(myfn, 17,19,14,e=arg0)(3), (17,19,14,1,3))
t = [0,1,2,3]
test_eq(mapt(operator.neg, t), (0,-1,-2,-3))
test_eq(map_ex(t,operator.neg), [0,-1,-2,-3])
If f
is a string then it is treated as a format string to create the mapping:
test_eq(map_ex(t, '#{}#'), ['#0#','#1#','#2#','#3#'])
If f
is a dictionary (or anything supporting __getitem__
) then it is indexed to create the mapping:
test_eq(map_ex(t, list('abcd')), list('abcd'))
You can also pass the same arg
params that bind
accepts:
def f(a=None,b=None): return b
test_eq(map_ex(t, f, b=arg0), range(4))
f1 = lambda o,p=0: (o*2)+p
f2 = lambda o,p=1: (o+1)/p
test_eq(f2(f1(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))
f1.order = 1
test_eq(f1(f2(3)), compose(f1,f2, order="order")(3))
test_eq(maps([1]), [1])
test_eq(maps(operator.neg, [1,2]), [-1,-2])
test_eq(maps(operator.neg, operator.neg, [1,2]), [1,2])
def _f(x,a=1):
"test func"
return x-a
_f.order=1
f = partialler(_f, 2)
test_eq(f.order, 1)
test_eq(f(3), -1)
f = partialler(_f, a=2, order=3)
test_eq(f.__doc__, "test func")
test_eq(f.order, 3)
test_eq(f(3), _f(3,2))
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)
f = partial0(_f, 2)
test_eq(f.order, 1)
test_eq(f(3), 1) # NB: different to `partialler` example
test_eq_type(instantiate(int), 0)
test_eq_type(instantiate(1), 1)
t = Path('/a/b.txt')
f = using_attr(str.upper, 'name')
test_eq(f(t), 'B.TXT')
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()
.
f = Self.sum()
x = np.array([3.,1])
test_eq(f(x), 4.)
# This is equivalent to above
f = lambda o: o.sum()
x = np.array([3.,1])
test_eq(f(x), 4.)
f = Self.argmin()
arr = np.array([1,2,3,4,5])
test_eq(f(arr), arr.argmin())
f = Self.sum().is_integer()
x = np.array([3.,1])
test_eq(f(x), True)
f = Self.sum().real.is_integer()
x = np.array([3.,1])
test_eq(f(x), True)
f = Self.imag()
test_eq(f(3), 0)
f = Self[1]
test_eq(f(x), 1)
def f(a, b=3): return a+b+2
def g(a, b=3): return a*b
fg = Self(1,b=2)
list(map(fg, [f,g]))
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
a = cp.copy(foo)
b = cp.deepcopy(foo)
a.someattr = 'hello' # since a and b point at the same object, updating a will update b
test_eq(b.someattr, 'hello')
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:
c = copy_func(foo) # c is an indpendent object
assert c is not foo
def g(x, *, y=3):
return x+y
test_eq(copy_func(g)(4), 7)
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
t = _T3(1) # we initilized `t` to a type int = 1
test_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3
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
t = _T4('hello ') # this sets self.g = 'helllo '
test_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello '
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
test_eq(_T5.func(4), 7)
Additionally you can specify that the function you want to patch should be a class attribute with as_prop
= False
@patch_to(_T5, as_prop=True)
def add_ten(self): return self + 10
t = _T5(4)
test_eq(t.add_ten, 14)
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
t = _T6(2)
test_eq(t.func_mult(4), 8)
t = _T7(2)
test_eq(t.func_mult(4), 8)
@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
t = _T8(1) # we initilized `t` to a type int = 1
test_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4
test_eq(t.func.__qualname__, '_T8.func')
Similarly to patch_to
, you can supply a tuple 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
t = _T8(2)
test_eq(t.func2(4), 8)
test_eq(t.func2.__qualname__, '_T8.func2')
t = _T9(2)
test_eq(t.func2(4), 8)
test_eq(t.func2.__qualname__, '_T9.func2')
@patch(as_prop=True)
def add_ten(self:_T5): return self + 10
t = _T5(4)
test_eq(t.add_ten, 14)
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
test_eq(_T5.func(4), 7)
_T = ImportEnum('_T', {'foobar':1, 'goobar':2})
_T.imports()
test_eq(foobar, _T.foobar)
test_eq(goobar, _T.goobar)
_T = str_enum('_T', 'a', 'b')
test_eq(f'{_T.a}', 'a')
test_eq(_T.a, 'a')
test_eq(list(_T.__members__), ['a','b'])
print(_T.a, _T.a.upper())
class _T(Stateful):
def __init__(self):
super().__init__()
self.a=1
self._state['test']=2
t = _T()
t2 = pickle.loads(pickle.dumps(t))
test_eq(t.a,1)
test_eq(t._state['test'],2)
test_eq(t2.a,1)
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."""
_stateattrs=('file',)
def __init__(self, filename):
self.filename,self.lineno = filename,0
super().__init__()
def readline(self):
self.lineno += 1
line = self.file.readline()
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()
reader = TextReader("00_test.ipynb")
print(reader.readline())
print(reader.readline())
new_reader = pickle.loads(pickle.dumps(reader))
print(reader.readline())
Allow strings with special characters to render properly in Jupyter. Without calling print()
strings with special characters are displayed like so:
with_special_chars='a string\nwith\nnew\nlines and\ttabs'
with_special_chars
We can correct this with PrettyString
:
PrettyString(with_special_chars)
test_eq(even_mults(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)
num_cpus()
class _T(): a,b = add_props(lambda i,x:i*2)
t = _T()
test_eq(t.a,0)
test_eq(t.b,2)
class _T():
def __init__(self, v): self.v=v
def _set(i, self, v): self.v[i] = v
a,b = add_props(lambda i,x: x.v[i], _set)
t = _T([0,2])
test_eq(t.a,0)
test_eq(t.b,2)
t.a = t.a+1
t.b = 3
test_eq(t.a,1)
test_eq(t.b,3)
@typed
def discount(price:int, pct:float):
return (1-pct) * price
with ExceptionExpected(TypeError): discount(100.0, .1)
We can also optionally allow multiple types by enumarating the types in a tuple as illustrated below:
def discount(price:(int,float), pct:float):
return (1-pct) * price
assert 90.0 == discount(100.0, .1)
@typed
def foo(a:int, b:str='a'): return a
test_eq(foo(1, '2'), 1)
with ExceptionExpected(TypeError): foo(1,2)
@typed
def foo()->str: return 1
with ExceptionExpected(TypeError): foo()
@typed
def foo()->str: return '1'
assert foo()
typed
works with classes, too:
class Foo:
@typed
def __init__(self, a:int, b: int, c:str): pass
@typed
def test(cls, d:str): return d
with ExceptionExpected(TypeError): Foo(1, 2, 3)
with ExceptionExpected(TypeError): Foo(1,2, 'a string').test(10)
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