class T:
def foo(self): pass
def bar(self): passFoundation
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:
add_docs(T, cls_doc="A docstring for the class.",
foo="The foo method.",
bar="The bar method.")Now, docstrings will appear as expected:
test_eq(T.__doc__, "A docstring for the class.")
test_eq(T.foo.__doc__, "The foo method.")
test_eq(T.bar.__doc__, "The bar method.")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
f=lambda: add_docs(T, "A docstring for the class.", foo="The foo method.")
test_fail(f, contains="Missing docs")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
_docs = dict(cls_doc="The class docstring",
f="The docstring for method f.",
g="A different docstring for method g.")
test_eq(_T.__doc__, "The class docstring")
test_eq(_T.f.__doc__, "The docstring for method f.")
test_eq(_T.g.__doc__, "A different docstring for method g.")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
_docs = dict(f="The docstring for method f.")
test_eq(_T.__doc__, "The class docstring")
test_eq(_T.f.__doc__, "The docstring for method f.")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:
test_eq(coll_repr(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]')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
test_eq(mask2idxs([False,True,False,True]), [1,3])
test_eq(mask2idxs(array([False,True,False,True])), [1,3])
test_eq(mask2idxs(array([1,2,3])), [1,2,3])cycle
cycle (o)
Like itertools.cycle except creates list of Nones if o is empty
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])zip_cycle
zip_cycle (x, *args)
Like itertools.zip_longest but cycles through elements of all but first argument
test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])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]]))product
product (xs)
The product of elements of xs, with Nones removed
product([None, 3, 4, 5])60
product([])1
sum([])0
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
l = _T([1,2,3,4,5])
test_eq(len(l), 5) # __len__
test_eq(l[-1], 5); test_eq(l[0], 1) #__getitem__
l[2] = 100; test_eq(l[2], 100) # __set_item__
del l[0]; test_eq(len(l), 4) # __delitem__
test_eq(str(l), '[2, 100, 4, 5]') # __repr__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.
from fastcore.utils import gtRead 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.
t = L(range(12))
test_eq(t, list(range(12)))
test_ne(t, list(range(11)))
t[3] = "h"
test_eq(t[3], "h")
t[3,5] = ("j","k")
test_eq(t[3,5], ["j","k"])
test_eq(t, L(t))
test_eq(L(L(1,2),[3,4]), ([1,2],[3,4]))
t(#12) [0,1,2,'j',4,'k',6,7,8,9,10,11]
Any L is a Sequence so you can use it with methods like random.sample:
assert isinstance(t, Sequence)import randomrandom.seed(0)
random.sample(t, 3)[6, 11, 0]
There are optimized indexers for arrays, tensors, and DataFrames.
import pandas as pdarr = np.arange(9).reshape(3,3)
t = L(arr, use_list=None)
test_eq(t[1,2], arr[[1,2]])
df = pd.DataFrame({'a':[1,2,3]})
t = L(df, use_list=None)
test_eq(t[1,2], L(pd.DataFrame({'a':[2,3]}, index=[1,2]), use_list=None))You can also modify an L with append, +, and *.
t = L()
test_eq(t, [])
t.append(1)
test_eq(t, [1])
t += [3,2]
test_eq(t, [1,3,2])
t = t + [4]
test_eq(t, [1,3,2,4])
t = 5 + t
test_eq(t, [5,1,3,2,4])
test_eq(L(1,2,3), [1,2,3])
test_eq(L(1,2,3), L(1,2,3))
t = L(1)*5
test_eq(~L([True,False,False]), L([False,True,True]))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.
test_eq(L([1,2,3]),[1,2,3])
test_eq(L(L([1,2,3])),[1,2,3])
test_ne(L([1,2,3]),[1,2,])
test_eq(L('abc'),['abc'])
test_eq(L(range(0,3)),[0,1,2])
test_eq(L(o for o in range(0,3)),[0,1,2])
test_eq(L(array(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/arraysIf match is not None then the created list is same len as match, either by:
- If
len(items)==1thenitemsis replicated, - Otherwise an error is raised if
matchanditemsare not already the same size.
test_eq(L(1,match=[1,2,3]),[1,1,1])
test_eq(L([1,2],match=[2,3]),[1,2])
test_fail(lambda: L([1,2],match=[1,2,3]))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.
test_eq(L(['a', 'b']), ['a', 'b'])
test_ne(L(['a', 'b']), 'ab')
test_ne(L(['a', 'b']), {'a':1, 'b':2})L Methods
L.__getitem__
L.__getitem__ (idx)
Retrieve idx (can be list of indices, or mask, or int) items
t = L(range(12))
test_eq(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[array(3)], 3)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)
t[4,6] = 0
test_eq(t[4,6], [0,0])
t[4,6] = [1,2]
test_eq(t[4,6], [1,2])L.unique
L.unique (sort=False, bidir=False, start=None)
Unique items, in stable order
test_eq(L(4,1,2,3,4,4).unique(), [4,1,2,3])L.val2idx
L.val2idx ()
Dict from value to index
test_eq(L(1,2,3).val2idx(), {3:2,1:0,2:1})L.split
L.split (s, sep=None, maxsplit=-1)
Class Method: Same as str.split, but returns an L
L.split is a class method that works like str.split, but returns an L instead of a list:
test_eq(L.split('a b c'), ['a','b','c'])
test_eq(L.split('a-b-c', '-'), ['a','b','c'])
test_eq(L.split('a-b-c', '-', maxsplit=1), ['a','b-c'])L.splitlines
L.splitlines (s, keepends=False)
Class Method: Same as str.splitlines, but returns an L
L.splitlines is a class method that works like str.splitlines, but returns an L instead of a list:
test_eq(L.splitlines('a\nb\nc'), ['a','b','c'])
test_eq(L.splitlines('a\nb\nc', keepends=True), ['a\n','b\n','c'])L.groupby
L.groupby (key, val=<function noop>)
Same as fastcore.basics.groupby
words = L.split('aaa abc bba')
test_eq(words.groupby(0, (1,2)), {'a':[('a','a'),('b','c')], 'b':[('b','a')]})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]
test_eq(t.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])L.starfilter
L.starfilter (f, negate=False, **kwargs)
Like filter, but unpacks elements as args to f
L.starfilter is like filter, but unpacks tuple elements as arguments to the predicate:
test_eq(L((1,2),(3,1),(2,3)).starfilter(lt), [(1,2),(2,3)])
test_eq(L((1,2),(3,1),(2,3)).starfilter(lt, negate=True), [(3,1)])L.rstarfilter
L.rstarfilter (f, negate=False, **kwargs)
Like starfilter, but reverse the order of args
L.rstarfilter is like starfilter, but reverses the order of unpacked arguments:
test_eq(L((2,1),(1,3),(3,2)).rstarfilter(lt), [(2,1),(3,2)]) # 1<2, 3<1 fails, 2<3
test_eq(L((2,1),(1,3),(3,2)).rstarfilter(lt, negate=True), [(1,3)])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)
test_eq_type(L.range([1,1,1]), L(range(3)))
test_eq_type(L.range(5,2,2), L(range(5,2,2)))L.argwhere
L.argwhere (f, negate=False, **kwargs)
Like filter, but return indices for matching items
test_eq(t.argwhere(lambda o:o<5), [0,1,2,3,4,6])L.starargwhere
L.starargwhere (f, negate=False)
Like argwhere, but unpacks elements as args to f
L.starargwhere is like argwhere, but unpacks tuple elements as arguments to the predicate:
test_eq(L((1,2),(3,1),(2,3)).starargwhere(lt), [0,2])
test_eq(L((1,2),(3,1),(2,3)).starargwhere(lt, negate=True), [1])L.rstarargwhere
L.rstarargwhere (f, negate=False)
Like starargwhere, but reverse the order of args
L.rstarargwhere is like starargwhere, but reverses the order of unpacked arguments:
test_eq(L((2,1),(1,3),(3,2)).rstarargwhere(lt), [0,2]) # 1<2, 3<1 fails, 2<3
test_eq(L((2,1),(1,3),(3,2)).rstarargwhere(lt, negate=True), [1])L.enumerate
L.enumerate ()
Same as enumerate
test_eq(L('a','b','c').enumerate(), [(0,'a'),(1,'b'),(2,'c')])L.renumerate
L.renumerate ()
Same as renumerate
test_eq(L('a','b','c').renumerate(), [('a', 0), ('b', 1), ('c', 2)])L.argfirst
L.argfirst (f, negate=False)
Return index of first matching item
test_eq(t.argfirst(lambda o:o>4), 5)
test_eq(t.argfirst(lambda o:o>4,negate=True),0)L.starargfirst
L.starargfirst (f, negate=False)
Like argfirst, but unpacks elements as args to f
L.starargfirst is like argfirst, but unpacks tuple elements as arguments to the predicate:
test_eq(L((3,1),(1,2),(2,3)).starargfirst(lt), 1)
test_eq(L((1,2),(3,1),(2,3)).starargfirst(lt, negate=True), 1)L.rstarargfirst
L.rstarargfirst (f, negate=False)
Like starargfirst, but reverse the order of args
L.rstarargfirst is like starargfirst, but reverses the order of unpacked arguments:
test_eq(L((1,3),(2,1),(3,2)).rstarargfirst(lt), 1) # 3<1 fails, 1<2
test_eq(L((2,1),(1,3),(3,2)).rstarargfirst(lt, negate=True), 1)L.map
L.map (f, *args, **kwargs)
Create new L with f applied to all items, passing args and kwargs to f
test_eq(L.range(4).map(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(L.range(4).map('#{}#'), ['#0#','#1#','#2#','#3#'])If f is a dictionary (or anything supporting __getitem__) then it is indexed to create the mapping:
test_eq(L.range(4).map(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(L.range(4).map(f, b=arg0), range(4))L.starmap
L.starmap (f, *args, **kwargs)
Like map, but use itertools.starmap
L.starmap applies a function to each element, unpacking tuples as arguments:
test_eq(L([(1,2),(3,4)]).starmap(operator.add), [3,7])
test_eq(L([(1,2,3),(4,5,6)]).starmap(lambda a,b,c: a+b*c), [7,34])L.rstarmap
L.rstarmap (f, *args, **kwargs)
Like starmap, but reverse the order of args
L.rstarmap is like starmap, but reverses the order of unpacked arguments:
test_eq(L((1,2),(3,4)).rstarmap(operator.sub), [1,1]) # 2-1, 4-3
test_eq(L(('a','b'),('c','d')).rstarmap('{}{}'.format), ['ba','dc'])L.map_dict
L.map_dict (f=<function noop>, *args, **kwargs)
Like map, but creates a dict from items to function results
test_eq(L(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})L.zip
L.zip (cycled=False)
Create new L with zip(*items)
t = L([[1,2,3],'abc'])
test_eq(t.zip(), [(1, 'a'),(2, 'b'),(3, 'c')])t = L([[1,2,3,4],['a','b','c']])
test_eq(t.zip(cycled=True ), [(1, 'a'),(2, 'b'),(3, 'c'),(4, 'a')])
test_eq(t.zip(cycled=False), [(1, 'a'),(2, 'b'),(3, 'c')])L.map_zip
L.map_zip (f, *args, cycled=False, **kwargs)
Combine zip and starmap
t = L([1,2,3],[2,3,4])
test_eq(t.map_zip(operator.mul), [2,6,12])L.zipwith
L.zipwith (*rest, cycled=False)
Create new L with self zip with each of *rest
b = [[0],[1],[2,2]]
t = L([1,2,3]).zipwith(b)
test_eq(t, [(1,[0]), (2,[1]), (3,[2,2])])L.map_zipwith
L.map_zipwith (f, *rest, cycled=False, **kwargs)
Combine zipwith and starmap
test_eq(L(1,2,3).map_zipwith(operator.mul, [2,3,4]), [2,6,12])L.itemgot
L.itemgot (*idxs)
Create new L with item idx of all items
test_eq(t.itemgot(1), b)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
a = [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)]
test_eq(L(a).attrgot('b'), [4,2])
#Example of when items are a dict
b =[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}]
test_eq(L(b).attrgot('id'), [15, 17])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
test_eq(L(a).sorted('a').attrgot('b'), [2,4])L.starsorted
L.starsorted (key, reverse=False)
Like sorted, but unpacks elements as args to key
L.starsorted is like sorted, but unpacks tuple elements as arguments to the key function:
test_eq(L((3,1),(1,2),(2,0)).starsorted(operator.sub), [(1,2),(3,1),(2,0)]) # sorted by a-b: 2, 2, -1
test_eq(L((1,2),(3,1),(2,3)).starsorted(operator.add), [(1,2),(3,1),(2,3)]) # sorted by a+b: 3, 4, 5L.rstarsorted
L.rstarsorted (key, reverse=False)
Like starsorted, but reverse the order of args
L.rstarsorted is like starsorted, but reverses the order of unpacked arguments:
test_eq(L((1,3),(2,1),(0,2)).rstarsorted(operator.sub), [(2,1),(1,3),(0,2)]) # sorted by b-a: 0, 2, 2
test_eq(L((2,1),(1,3),(3,2)).rstarsorted(operator.sub), [(2,1),(3,2),(1,3)]) # sorted by b-a: -1, -1, 2L.concat
L.concat ()
Concatenate all elements of list
test_eq(L([0,1,2,3],4,L(5,6)).concat(), range(7))L.copy
L.copy ()
Same as list.copy, but returns an L
t = L([0,1,2,3],4,L(5,6)).copy()
test_eq(t.concat(), range(7))L.shuffle
L.shuffle ()
Same as random.shuffle, but not inplace
L.shuffle returns a new shuffled L, leaving the original unchanged:
t = L(1,2,3,4,5)
s = t.shuffle()
test_eq(set(s), set(t)) # same elements
test_eq(t, [1,2,3,4,5]) # original unchangedL.reduce
L.reduce (f, initial=None)
Wrapper for functools.reduce
test_eq(L(1,2,3,4).reduce(operator.add), 10)
test_eq(L(1,2,3,4).reduce(operator.mul, 10), 240)L.starreduce
L.starreduce (f, initial=None)
Like reduce, but unpacks elements as args to f
L.starreduce is like reduce, but unpacks tuple elements as additional arguments to f (after accumulator):
test_eq(L((1,2),(3,4),(5,6)).starreduce(lambda acc,a,b: acc+a*b, 0), 44) # 0+1*2+3*4+5*6
test_eq(L(('a',1),('b',2)).starreduce(lambda acc,k,v: {**acc, k:v}, {}), {'a':1,'b':2})E.g implement a dot product:
def dot(a,b): return a.zipwith(b).starreduce(lambda acc,a,b: acc+a*b, 0)
dot(L(1,3,5), L(2,4,6))44
L.rstarreduce
L.rstarreduce (f, initial=None)
Like starreduce, but reverse the order of unpacked args
L.rstarreduce is like starreduce, but reverses the order of unpacked arguments:
L.sum
L.sum ()
Sum of the items
test_eq(L(1,2,3,4).sum(), 10)
test_eq(L().sum(), 0)L.product
L.product ()
Product of the items
test_eq(L(1,2,3,4).product(), 24)
test_eq(L().product(), 1)L.map_first
L.map_first (f=<function noop>, g=<function noop>, *args, **kwargs)
First element of map_filter
t = L(0,1,2,3)
test_eq(t.map_first(lambda o:o*2 if o>2 else None), 6)L.setattrs
L.setattrs (attr, val)
Call setattr on all items
t = L(SimpleNamespace(),SimpleNamespace())
t.setattrs('foo', 'bar')
test_eq(t.attrgot('foo'), ['bar','bar'])itertools wrappers
L.cycle
L.cycle ()
Same as itertools.cycle
L.cycle returns an infinite iterator that cycles through the elements:
test_eq(list(itertools.islice(L(1,2,3).cycle(), 7)), [1,2,3,1,2,3,1])L.takewhile
L.takewhile (f)
Same as itertools.takewhile
L.takewhile returns elements from the beginning of the list while the predicate is true:
test_eq(L(1,2,3,4,5,1,2).takewhile(lambda x: x<4), [1,2,3])
test_eq(L(1,2,3,11).takewhile(lt(10)), [1,2,3])L.dropwhile
L.dropwhile (f)
Same as itertools.dropwhile
L.dropwhile skips elements from the beginning while the predicate is true, then returns the rest:
test_eq(L(1,2,3,4,5,1,2).dropwhile(lt(4)), [4,5,1,2])
test_eq(L(1,2,3).dropwhile(lt(10)), [])L.startakewhile
L.startakewhile (f)
Like takewhile, but unpacks elements as args to f
L.startakewhile is like takewhile, but unpacks tuple elements as arguments to the predicate:
test_eq(L((1,2),(2,3),(4,1),(5,6)).startakewhile(lambda a,b: a<b), [(1,2),(2,3)])
test_eq(L((1,10),(2,20),(5,3)).startakewhile(lt), [(1,10),(2,20)])L.rstartakewhile
L.rstartakewhile (f)
Like startakewhile, but reverse the order of args
L.rstartakewhile is like startakewhile, but reverses the order of unpacked arguments:
test_eq(L((2,1),(3,2),(1,4),(6,5)).rstartakewhile(lt), [(2,1),(3,2)]) # 1<2, 2<3, 4<1 fails
test_eq(L((10,1),(20,2),(3,5)).rstartakewhile(lt), [(10,1),(20,2)]) # 1<10, 2<20, 5<3 failsL.stardropwhile
L.stardropwhile (f)
Like dropwhile, but unpacks elements as args to f
L.stardropwhile is like dropwhile, but unpacks tuple elements as arguments to the predicate:
test_eq(L((1,2),(2,3),(4,1),(5,6)).stardropwhile(lambda a,b: a<b), [(4,1),(5,6)])
test_eq(L((1,10),(2,20),(5,3)).stardropwhile(lt), [(5,3)])L.rstardropwhile
L.rstardropwhile (f)
Like stardropwhile, but reverse the order of args
L.rstardropwhile is like stardropwhile, but reverses the order of unpacked arguments:
test_eq(L((2,1),(3,2),(1,4),(6,5)).rstardropwhile(lt), [(1,4),(6,5)]) # 1<2, 2<3 pass, 4<1 fails
test_eq(L((10,1),(20,2),(3,5)).rstardropwhile(lt), [(3,5)])L.accumulate
L.accumulate (f=<built-in function add>, initial=None)
Same as itertools.accumulate
L.accumulate returns running totals (or running results of any binary function):
test_eq(L(1,2,3,4).accumulate(), [1,3,6,10])
test_eq(L(1,2,3,4).accumulate(operator.mul), [1,2,6,24])
test_eq(L(1,2,3).accumulate(initial=10), [10,11,13,16])L.pairwise
L.pairwise ()
Same as itertools.pairwise
L.pairwise returns consecutive overlapping pairs:
test_eq(L(1,2,3,4).pairwise(), [(1,2),(2,3),(3,4)])
test_eq(L(list('abcd')).pairwise(), [('a','b'),('b','c'),('c','d')])L.batched
L.batched (n)
Same as itertools.batched
L.batched splits into chunks of size n:
test_eq(L(1,2,3,4,5).batched(2), [(1,2),(3,4),(5,)])
test_eq(L(list('abcdefg')).batched(3), [('a','b','c'),('d','e','f'),('g',)])L.compress
L.compress (selectors)
Same as itertools.compress
L.compress filters elements using a boolean selector:
test_eq(L(list('abcd')).compress([1,0,1,0]), ['a','c'])
test_eq(L(1,2,3,4,5).compress([True,False,True,False,True]), [1,3,5])L.permutations
L.permutations (r=None)
Same as itertools.permutations
L.permutations returns all permutations of length r (defaults to full length):
test_eq(L(1,2,3).permutations(), [(1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1)])
test_eq(L(list('abc')).permutations(2), [('a','b'),('a','c'),('b','a'),('b','c'),('c','a'),('c','b')])L.combinations
L.combinations (r)
Same as itertools.combinations
L.combinations returns all combinations of length r:
test_eq(L(1,2,3,4).combinations(2), [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)])
test_eq(L(list('abcd')).combinations(3), [('a','b','c'),('a','b','d'),('a','c','d'),('b','c','d')])L.partition
L.partition (f=<function noop>, **kwargs)
Split into two Ls based on predicate f: (true_items, false_items)
L.partition splits a list into two Ls based on a predicate—items where f returns true, and items where it returns false:
t,f = L(1,2,3,4,5,6).partition(lambda x: x%2==0)
test_eq(t, [2,4,6])
test_eq(f, [1,3,5])
evens,odds = L.range(10).partition(lambda x: x%2==0)
test_eq(evens, [0,2,4,6,8])
test_eq(odds, [1,3,5,7,9])L.starpartition
L.starpartition (f, **kwargs)
Like partition, but unpacks elements as args to f
L.starpartition is like partition, but unpacks tuple elements as arguments to the predicate:
asc,desc = L((1,2),(3,1),(2,4),(5,3)).starpartition(lt)
test_eq(asc, [(1,2),(2,4)]) # a < b
test_eq(desc, [(3,1),(5,3)]) # a >= bL.rstarpartition
L.rstarpartition (f, **kwargs)
Like starpartition, but reverse the order of args
L.rstarpartition is like starpartition, but reverses the order of unpacked arguments:
asc,desc = L((2,1),(1,3),(4,2),(3,5)).rstarpartition(lt)
test_eq(asc, [(2,1),(4,2)]) # b < a (i.e., 1<2, 2<4)
test_eq(desc, [(1,3),(3,5)]) # b >= aL.flatten
L.flatten ()
Recursively flatten nested iterables (except strings)
L.flatten recursively flattens nested iterables into a single L. Strings are treated as atomic (not iterated over):
test_eq(L([[1,2],[3,[4,5]]]).flatten(), [1,2,3,4,5])
test_eq(L([1,[2,[3,[4]]]]).flatten(), [1,2,3,4])
test_eq(L(['a',['b','c'],'d']).flatten(), ['a','b','c','d']) # strings not flattened
test_eq(L([1,2,3]).flatten(), [1,2,3]) # already flatConfig
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.
_d = dict(user='fastai', lib_name='fastcore', some_path='test', some_bool=True, some_num=3)
try:
save_config_file('tmp.ini', _d)
res = read_config_file('tmp.ini')
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, **cfg_kwargs)
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:
save_config_file('../tmp.ini', _d)
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:
cfg = Config('..', 'tmp.ini', create=_d, save=False)
test_eq(cfg.user,'fastai')
assert not Path('../tmp.ini').exists()You can also pass in ConfigParser kwargs to change the behavior of how your configuration file will be parsed. For example, by default, inline comments are not handled by Config. However, if you pass in the inline_comment_prefixes with whatever your comment symbol is, you’ll overwrite this behavior.
# Create a complete example config file with comments
cfg_str = """\
[DEFAULT]
user = fastai # inline comment
# Library configuration
lib_name = fastcore
# Paths
some_path = test
# Feature flags
some_bool = True
# Numeric settings
some_num = # missing value
"""
with open('../tmp.ini', 'w') as f:
f.write(cfg_str)# Now read it back to verify
try: cfg = Config('..', 'tmp.ini', inline_comment_prefixes=('#'))
finally: os.unlink('../tmp.ini')
test_eq(cfg.user,'fastai')
test_eq(cfg.some_num,'')Config.get
Config.get (k, default=None)
Keys can be accessed as attributes, items, or with get and an optional default:
test_eq(cfg.user,'fastai')
test_eq(cfg['some_path'], 'test')
test_eq(cfg.get('foo','bar'),'bar')Extra files can be read before cfg_path/cfg_name using extra_files, in the order they appear:
with tempfile.TemporaryDirectory() as d:
a = Config(d, 'a.ini', {'a':0,'b':0})
b = Config(d, 'b.ini', {'a':1,'c':0})
c = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file])
test_eq(c.d, {'a':'2','b':'0','c':'0','d':'0'})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.
_types = dict(some_path=Path, some_bool=bool, some_num=int)
cfg = Config('..', 'tmp.ini', create=_d, save=False, types=_types)
test_eq(cfg.user,'fastai')
test_eq(cfg['some_path'].resolve(), (Path('..')/'test').resolve())
test_eq(cfg.get('some_num'), 3)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:
Config.find('settings.ini').repo'fastcore'