add_docs attaches docstrings to a class and its methods from a dictionary. docs is its decorator form. Both check that every public method has documentation.
coll_repr shows a collection’s length and a preview of its items. flatmap(f, xs) applies f to each item and concatenates the results.
Copy values from docs to cls docstrings, and confirm all public methods are documented
add_docs groups a class’s docstrings separately from its method definitions. This lets you document one-line methods without expanding their definitions. We discuss this style in our style guide.
Suppose you have the following undocumented class:
class T:def foo(self): passdef bar(self): pass
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): passdef bar(self): passwith expect_fail(Exception, "Missing docs"): add_docs(T, "A docstring for the class.", foo="The foo method.")
To use the docs decorator, put the docstrings in the class’s _docs dictionary. Use method names as keys and cls_doc for the class docstring:
@docsclass _T:def f(self): passdef 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:
@docsclass _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
def is_iter( o):
Test whether o can be used in a for loop
assert is_iter([1])assertnot is_iter(array(1))assert is_iter(array([1,2]))assert (o for o inrange(3))
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.
Behaves like a list of items but can also index with list of indices or masks
L extends Python’s list operations with indexing inspired by NumPy. You can index with an integer, slice, collection of integers or boolean mask. Methods such as map, filter, sorted, unique, itemgot and attrgot return an L:
Examples and overview
from fastcore.utils import gt
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.
You can construct an L from an iterable. By default, L keeps a tensor or array as one item. Pass use_list=True to collect its elements instead:
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 inrange(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/arrays
If match is not None then the created list is same len as match, either by:
If len(items)==1 then items is replicated,
Otherwise an error is raised if match and items are not already the same size.
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.
Slicing or selecting from an L subclass returns the same subclass:
class _Rows(L):def__repr__(self): return'\n'.join(f'row {o}'for o inself)r = _Rows(range(5))for sel in (slice(1,3), [0,2], [True,False,True,False,True], (1,2)): test_eq(type(r[sel]), _Rows)test_eq(r[1], 1) # a single item is still the item, not a collectionr[1:3]
row 1
row 2
Override _new to preserve subclass attributes in results such as slices and concatenations. It receives only the resulting items. Here _Titled copies its title when slicing or adding items:
class _Titled(L):def__init__(self, items=None, title=None, **kw):super().__init__(items, **kw)self.title = titledef _new(self, items, *args, **kw): res =super()._new(items, *args, **kw) res.title =self.titlereturn restl = _Titled(range(5), title='counts')test_eq(tl[1:3].title, 'counts')test_eq((tl + [5]).title, 'counts') # one hook covers every derived collectiontest_eq(_Titled(range(3))[:2].title, None)
Keep per-item state on the items themselves. _new doesn’t receive the selected indices and cannot select corresponding entries from a separate attribute.
self.items stores the collection’s contents. Use another name for subclass attributes.
_new(items) must preserve the items it receives. Subclasses must also preserve indexing and iteration behavior. For a single index, x[i] must return x.items[i]. list(x) must equal list(x.items). Methods such as map and filter depend on these rules. For a lazy view that transforms items on access, store an L as an attribute instead of subclassing it.
To select raw items into a plain L, use L(x.items, use_list=None)[idx]. use_list=None preserves an array-backed collection. Calling super().__getitem__ can still invoke overridden methods.
Call a curryable method on the class to create a function that accepts an iterable later. For example, you can convert each nested list with a lambda:
L(lines).map(lambda x: L(x).map(int))
Or pass the curried L.map(int):
L(lines).map(L.map(int))
Curried calls also work with adapters such as star. To multiply the pairs inside each sublist:
nested.map(L.map(star(operator.mul)))
map
defmap( f, *args, **kwargs):
Create new L with f applied to all items, passing args and kwargs to f
t = L(1,2,3)t.append(4)test_eq(t[[0,3]], [1,4])test_eq(t[[False,True,False,True]], [2,4])test_eq(t.map(lambda o:o*2), [2,4,6,8])
Call L.map(int) on the class to get a function that converts an iterable’s items to integers. This curried form works with map, filter, groupby, argwhere, argfirst, first, last, sorted, reduce, partition, takewhile, dropwhile and accumulate.
star(f) unpacks the last argument before calling f. In map, that argument is the item. In reduce, it is the item after the accumulator. rstar reverses the unpacked arguments.
Adapt f to unpack its last argument, e.g. for use in map-style functions
star(f) unpacks the last argument before calling f. Use it when each item contains multiple arguments. With reduce, the accumulator stays first and the item’s values follow it:
Create new L with attr k (or value k for dicts) of all items.
# Example when items are not a dicta = [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)]test_eq(L(a).attrgot('b'), [4,2])#Example of when items are a dictb =[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}]test_eq(L(b).attrgot('id'), [15, 17])