class T:
def foo(self): pass
def bar(self): pass
Foundation
L
class and helpers for it
Foundational Functions
working_directory
working_directory (path)
Change working directory to path
and return to previous on exit.
add_docs
add_docs (cls, cls_doc=None, **docs)
Copy values from docs
to cls
docstrings, and confirm all public methods are documented
add_docs
allows you to add docstrings to a class and its associated methods. This function allows you to group docstrings together seperate from your code, which enables you to define one-line functions as well as organize your code more succintly. We believe this confers a number of benefits which we discuss in our style guide.
Suppose you have the following undocumented class:
You can add documentation to this class like so:
="A docstring for the class.",
add_docs(T, cls_doc="The foo method.",
foo="The bar method.") bar
Now, docstrings will appear as expected:
"A docstring for the class.")
test_eq(T.__doc__, "The foo method.")
test_eq(T.foo.__doc__, "The bar method.") test_eq(T.bar.__doc__,
add_docs
also validates that all of your public methods contain a docstring. If one of your methods is not documented, it will raise an error:
class T:
def foo(self): pass
def bar(self): pass
=lambda: add_docs(T, "A docstring for the class.", foo="The foo method.")
f="Missing docs") test_fail(f, contains
docs
docs (cls)
Decorator version of add_docs
, using _docs
dict
Instead of using add_docs
, you can use the decorator docs
as shown below. Note that the docstring for the class can be set with the argument cls_doc
:
@docs
class _T:
def f(self): pass
def g(cls): pass
= dict(cls_doc="The class docstring",
_docs ="The docstring for method f.",
f="A different docstring for method g.")
g
"The class docstring")
test_eq(_T.__doc__, "The docstring for method f.")
test_eq(_T.f.__doc__, "A different docstring for method g.") test_eq(_T.g.__doc__,
For either the docs
decorator or the add_docs
function, you can still define your docstrings in the normal way. Below we set the docstring for the class as usual, but define the method docstrings through the _docs
attribute:
@docs
class _T:
"The class docstring"
def f(self): pass
= dict(f="The docstring for method f.")
_docs
"The class docstring")
test_eq(_T.__doc__, "The docstring for method f.") test_eq(_T.f.__doc__,
is_iter
is_iter (o)
Test whether o
can be used in a for
loop
assert is_iter([1])
assert not is_iter(array(1))
assert is_iter(array([1,2]))
assert (o for o in range(3))
coll_repr
coll_repr (c, max_n=20)
String repr of up to max_n
items of (possibly lazy) collection c
coll_repr
is used to provide a more informative __repr__
about list-like objects. coll_repr
and is used by L
to build a __repr__
that displays the length of a list in addition to a preview of a list.
Below is an example of the __repr__
string created for a list of 1000 elements:
range(1000),10), '(#1000) [0,1,2,3,4,5,6,7,8,9...]')
test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]')
test_eq(coll_repr(range(10), 5), '(#10) [0,1,2,3,4...]')
test_eq(coll_repr(range(5), 5), '(#5) [0,1,2,3,4]') test_eq(coll_repr(
is_bool
is_bool (x)
Check whether x
is a bool or None
mask2idxs
mask2idxs (mask)
Convert bool mask or index list to index L
False,True,False,True]), [1,3])
test_eq(mask2idxs([False,True,False,True])), [1,3])
test_eq(mask2idxs(array([1,2,3])), [1,2,3]) test_eq(mask2idxs(array([
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([
is_indexer
is_indexer (idx)
Test whether idx
will index a single item in a list
You can, for example index a single item in a list with an integer or a 0-dimensional numpy array:
assert is_indexer(1)
assert is_indexer(np.array(1))
However, you cannot index into single item in a list with another list or a numpy array with ndim > 0.
assert not is_indexer([1, 2])
assert not is_indexer(np.array([[1, 2], [3, 4]]))
L
helpers
CollBase
CollBase (items)
Base class for composing a list of items
ColBase
is a base class that emulates the functionality of a python list
:
class _T(CollBase): pass
= _T([1,2,3,4,5])
l
len(l), 5) # __len__
test_eq(-1], 5); test_eq(l[0], 1) #__getitem__
test_eq(l[2] = 100; test_eq(l[2], 100) # __set_item__
l[del l[0]; test_eq(len(l), 4) # __delitem__
str(l), '[2, 100, 4, 5]') # __repr__ test_eq(
L
L (x=None, *args, **kwargs)
Behaves like a list of items
but can also index with list of indices or masks
L
is a drop in replacement for a python list
. Inspired by NumPy, L
, supports advanced indexing and has additional methods (outlined below) that provide additional functionality and encourage simple expressive code. For example, the code below takes a list of pairs, selects the second item of each pair, takes its absolute value, filters items greater than 4, and adds them up:
from fastcore.utils import gt
= dict(a=1,b=-5,d=6,e=9).items()
d 1).map(abs).filter(gt(4)).sum(), 20) # abs(-5) + abs(6) + abs(9) = 20; 1 was filtered out. test_eq(L(d).itemgot(
Read this overview section for a quick tutorial of L
, as well as background on the name.
You can create an L
from an existing iterable (e.g. a list, range, etc) and access or modify it with an int list/tuple index, mask, int, or slice. All list
methods can also be used with L
.
= L(range(12))
t list(range(12)))
test_eq(t, list(range(11)))
test_ne(t,
t.reverse()0], 11)
test_eq(t[3] = "h"
t[3], "h")
test_eq(t[3,5] = ("j","k")
t[3,5], ["j","k"])
test_eq(t[
test_eq(t, L(t))1,2),[3,4]), ([1,2],[3,4]))
test_eq(L(L( t
(#12) [11,10,9,'j',7,'k',5,4,3,2,1,0]
Any L
is a Sequence
so you can use it with methods like random.sample
:
assert isinstance(t, Sequence)
import random
0)
random.seed(3) random.sample(t,
[5, 0, 11]
There are optimized indexers for arrays, tensors, and DataFrames.
import pandas as pd
= np.arange(9).reshape(3,3)
arr = L(arr, use_list=None)
t 1,2], arr[[1,2]])
test_eq(t[
= pd.DataFrame({'a':[1,2,3]})
df = L(df, use_list=None)
t 1,2], L(pd.DataFrame({'a':[2,3]}, index=[1,2]), use_list=None)) test_eq(t[
You can also modify an L
with append
, +
, and *
.
= L()
t
test_eq(t, [])1)
t.append(1])
test_eq(t, [+= [3,2]
t 1,3,2])
test_eq(t, [= t + [4]
t 1,3,2,4])
test_eq(t, [= 5 + t
t 5,1,3,2,4])
test_eq(t, [1,2,3), [1,2,3])
test_eq(L(1,2,3), L(1,2,3))
test_eq(L(= L(1)*5
t = t.map(operator.neg)
t -1]*5)
test_eq(t,[~L([True,False,False]), L([False,True,True]))
test_eq(= L(range(4))
t zip(t, L(1).cycle()), zip(range(4),(1,1,1,1)))
test_eq(= L.range(100)
t test_shuffled(t,t.shuffle())
sum(), 0)
test_eq(L([]).1) test_eq(L([]).product(),
def _f(x,a=0): return x+a
= L(1)*5
t map(_f), t)
test_eq(t.map(_f,1), [2]*5)
test_eq(t.map(_f,a=2), [3]*5) test_eq(t.
An L
can be constructed from anything iterable, although tensors and arrays will not be iterated over on construction, unless you pass use_list
to the constructor.
1,2,3]),[1,2,3])
test_eq(L([1,2,3])),[1,2,3])
test_eq(L(L([1,2,3]),[1,2,])
test_ne(L(['abc'),['abc'])
test_eq(L(range(0,3)),[0,1,2])
test_eq(L(for o in range(0,3)),[0,1,2])
test_eq(L(o 0)),[array(0)])
test_eq(L(array(0),array(1)]),[array(0),array(1)])
test_eq(L([array(0.,1.1]))[0],array([0.,1.1]))
test_eq(L(array([0.,1.1]), use_list=True), [array(0.),array(1.1)]) # `use_list=True` to unwrap arrays/arrays test_eq(L(array([
If match
is not None
then the created list is same len as match
, either by:
- If
len(items)==1
thenitems
is replicated, - Otherwise an error is raised if
match
anditems
are not already the same size.
1,match=[1,2,3]),[1,1,1])
test_eq(L(1,2],match=[2,3]),[1,2])
test_eq(L([lambda: L([1,2],match=[1,2,3])) test_fail(
If you create an L
from an existing L
then you’ll get back the original object (since L
uses the NewChkMeta
metaclass).
test_is(L(t), t)
An L
is considred equal to a list if they have the same elements. It’s never considered equal to a str
a set
or a dict
even if they have the same elements/keys.
'a', 'b']), ['a', 'b'])
test_eq(L(['a', 'b']), 'ab')
test_ne(L(['a', 'b']), {'a':1, 'b':2}) test_ne(L([
L
Methods
L.__getitem__
L.__getitem__ (idx)
Retrieve idx
(can be list of indices, or mask, or int) items
= L(range(12))
t 1,2], [1,2]) # implicit tuple
test_eq(t[1,2]], [1,2]) # list
test_eq(t[[3], [0,1,2]) # slice
test_eq(t[:False]*11 + [True]], [11]) # mask
test_eq(t[[3)], 3) test_eq(t[array(
L.__setitem__
L.__setitem__ (idx, o)
Set idx
(can be list of indices, or mask, or int) items to o
(which is broadcast if not iterable)
4,6] = 0
t[4,6], [0,0])
test_eq(t[4,6] = [1,2]
t[4,6], [1,2]) test_eq(t[
L.unique
L.unique (sort=False, bidir=False, start=None)
Unique items, in stable order
4,1,2,3,4,4).unique(), [4,1,2,3]) test_eq(L(
L.val2idx
L.val2idx ()
Dict from value to index
1,2,3).val2idx(), {3:2,1:0,2:1}) test_eq(L(
L.groupby
L.groupby (key, val=<function noop>)
Same as fastcore.basics.groupby
= L.split('aaa abc bba')
words 0, (1,2)), {'a':[('a','a'),('b','c')], 'b':[('b','a')]}) test_eq(words.groupby(
L.filter
L.filter (f=<function noop>, negate=False, **kwargs)
Create new L
filtered by predicate f
, passing args
and kwargs
to f
list(t)
[0, 1, 2, 3, 1, 5, 2, 7, 8, 9, 10, 11]
filter(lambda o:o<5), [0,1,2,3,1,2])
test_eq(t.filter(lambda o:o<5, negate=True), [5,7,8,9,10,11]) test_eq(t.
L.argwhere
L.argwhere (f, negate=False, **kwargs)
Like filter
, but return indices for matching items
lambda o:o<5), [0,1,2,3,4,6]) test_eq(t.argwhere(
L.argfirst
L.argfirst (f, negate=False)
Return index of first matching item
lambda o:o>4), 5)
test_eq(t.argfirst(lambda o:o>4,negate=True),0) test_eq(t.argfirst(
L.map
L.map (f, *args, **kwargs)
Create new L
with f
applied to all items
, passing args
and kwargs
to f
range(4).map(operator.neg), [0,-1,-2,-3]) test_eq(L.
If f
is a string then it is treated as a format string to create the mapping:
range(4).map('#{}#'), ['#0#','#1#','#2#','#3#']) test_eq(L.
If f
is a dictionary (or anything supporting __getitem__
) then it is indexed to create the mapping:
range(4).map(list('abcd')), list('abcd')) test_eq(L.
You can also pass the same arg
params that bind
accepts:
def f(a=None,b=None): return b
range(4).map(f, b=arg0), range(4)) test_eq(L.
L.map_dict
L.map_dict (f=<function noop>, *args, **kwargs)
Like map
, but creates a dict from items
to function results
range(1,5)).map_dict(), {1:1, 2:2, 3:3, 4:4})
test_eq(L(range(1,5)).map_dict(operator.neg), {1:-1, 2:-2, 3:-3, 4:-4}) test_eq(L(
L.zip
L.zip (cycled=False)
Create new L
with zip(*items)
= L([[1,2,3],'abc'])
t zip(), [(1, 'a'),(2, 'b'),(3, 'c')]) test_eq(t.
= L([[1,2,3,4],['a','b','c']])
t zip(cycled=True ), [(1, 'a'),(2, 'b'),(3, 'c'),(4, 'a')])
test_eq(t.zip(cycled=False), [(1, 'a'),(2, 'b'),(3, 'c')]) test_eq(t.
L.map_zip
L.map_zip (f, *args, cycled=False, **kwargs)
Combine zip
and starmap
= L([1,2,3],[2,3,4])
t 2,6,12]) test_eq(t.map_zip(operator.mul), [
L.zipwith
L.zipwith (*rest, cycled=False)
Create new L
with self
zip with each of *rest
= [[0],[1],[2,2]]
b = L([1,2,3]).zipwith(b)
t 1,[0]), (2,[1]), (3,[2,2])]) test_eq(t, [(
L.map_zipwith
L.map_zipwith (f, *rest, cycled=False, **kwargs)
Combine zipwith
and starmap
1,2,3).map_zipwith(operator.mul, [2,3,4]), [2,6,12]) test_eq(L(
L.itemgot
L.itemgot (*idxs)
Create new L
with item idx
of all items
1), b) test_eq(t.itemgot(
L.attrgot
L.attrgot (k, default=None)
Create new L
with attr k
(or value k
for dicts) of all items
.
# Example when items are not a dict
= [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)]
a 'b'), [4,2])
test_eq(L(a).attrgot(
#Example of when items are a dict
=[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}]
b 'id'), [15, 17]) test_eq(L(b).attrgot(
L.sorted
L.sorted (key=None, reverse=False, cmp=None, **kwargs)
New L
sorted by key
, using sort_ex
. If key is str use attrgetter
; if int use itemgetter
sorted('a').attrgot('b'), [2,4]) test_eq(L(a).
L.split
L.split (s, sep=None, maxsplit=-1)
Class Method: Same as str.split
, but returns an L
'a b c'), list('abc')) test_eq(L.split(
L.range
L.range (a, b=None, step=None)
Class Method: Same as range
, but returns L
. Can pass collection for a
, to use len(a)
range([1,1,1]), L(range(3)))
test_eq_type(L.range(5,2,2), L(range(5,2,2))) test_eq_type(L.
L.concat
L.concat ()
Concatenate all elements of list
0,1,2,3],4,L(5,6)).concat(), range(7)) test_eq(L([
L.copy
L.copy ()
Same as list.copy
, but returns an L
= L([0,1,2,3],4,L(5,6)).copy()
t range(7)) test_eq(t.concat(),
L.map_first
L.map_first (f=<function noop>, g=<function noop>, *args, **kwargs)
First element of map_filter
= L(0,1,2,3)
t lambda o:o*2 if o>2 else None), 6) test_eq(t.map_first(
L.setattrs
L.setattrs (attr, val)
Call setattr
on all items
= L(SimpleNamespace(),SimpleNamespace())
t 'foo', 'bar')
t.setattrs('foo'), ['bar','bar']) test_eq(t.attrgot(
Config
save_config_file
save_config_file (file, d, **kwargs)
Write settings dict to a new config file, or overwrite the existing one.
read_config_file
read_config_file (file, **kwargs)
Config files are saved and read using Python’s configparser.ConfigParser
, inside the DEFAULT
section.
= dict(user='fastai', lib_name='fastcore', some_path='test', some_bool=True, some_num=3)
_d try:
'tmp.ini', _d)
save_config_file(= read_config_file('tmp.ini')
res finally: os.unlink('tmp.ini')
dict(res)
{'user': 'fastai',
'lib_name': 'fastcore',
'some_path': 'test',
'some_bool': 'True',
'some_num': '3'}
Config
Config (cfg_path, cfg_name, create=None, save=True, extra_files=None, types=None)
Reading and writing ConfigParser
ini files
Config
is a convenient wrapper around ConfigParser
ini files with a single section (DEFAULT
).
Instantiate a Config
from an ini file at cfg_path/cfg_name
:
'../tmp.ini', _d)
save_config_file(try: cfg = Config('..', 'tmp.ini')
finally: os.unlink('../tmp.ini')
cfg
{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'}
You can create a new file if one doesn’t exist by providing a create
dict:
try: cfg = Config('..', 'tmp.ini', create=_d)
finally: os.unlink('../tmp.ini')
cfg
{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'}
If you additionally pass save=False
, the Config
will contain the items from create
without writing a new file:
= Config('..', 'tmp.ini', create=_d, save=False)
cfg 'fastai')
test_eq(cfg.user,assert not Path('../tmp.ini').exists()
Config.get
Config.get (k, default=None)
Keys can be accessed as attributes, items, or with get
and an optional default:
'fastai')
test_eq(cfg.user,'some_path'], 'test')
test_eq(cfg['foo','bar'),'bar') test_eq(cfg.get(
Extra files can be read before cfg_path/cfg_name
using extra_files
, in the order they appear:
with tempfile.TemporaryDirectory() as d:
= Config(d, 'a.ini', {'a':0,'b':0})
a = Config(d, 'b.ini', {'a':1,'c':0})
b = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file])
c 'a':'2','b':'0','c':'0','d':'0'}) test_eq(c.d, {
If you pass a dict types
, then the values of that dict will be used as types to instantiate all values returned. Path
is a special case – in that case, the path returned will be relative to the path containing the config file (assuming the value is relative). bool
types use str2bool
to convert to boolean.
= dict(some_path=Path, some_bool=bool, some_num=int)
_types = Config('..', 'tmp.ini', create=_d, save=False, types=_types)
cfg
'fastai')
test_eq(cfg.user,'some_path'].resolve(), (Path('..')/'test').resolve())
test_eq(cfg['some_num'), 3) test_eq(cfg.get(
Config.find
Config.find (cfg_name, cfg_path=None, **kwargs)
Search cfg_path
and its parents to find cfg_name
You can use Config.find
to search subdirectories for a config file, starting in the current path if no path is specified:
'settings.ini').repo Config.find(
'fastcore'