working_directory
def working_directory(
path
):Change working directory to path and return to previous on exit.
L class and helpers for it
add_docs (or the docs class decorator) attaches docstrings to a class and its methods from one dict kept separate from the code, so one-line methods stay one line; it also raises if any public method is left undocumented. coll_repr renders long list-likes as a length-prefixed preview like (#2000) [0, 1, ...], and flatmap(f, xs) is a named [y for x in xs for y in f(x)]: a map where each input may produce zero, one, or many outputs.
Change working directory to path and return to previous on exit.
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:
Now, docstrings will appear as expected:
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:
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:
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.
Like itertools.zip_longest but cycles through elements of all but first argument
You can, for example index a single item in a list with an integer or a 0-dimensional numpy array:
However, you cannot index into single item in a list with another list or a numpy array with ndim > 0.
Apply f to each element and flatten the results into a single list.
flatmap is a fundamental operation in functional programming that combines mapping and flattening into a single step. Where map applies a function to each element and returns a list of results, flatmap goes further: it expects the function to return a sequence for each element, then concatenates all those sequences into one flat list, which is useful for operations where each input naturally produces zero, one, or many outputs.
flatmap(f, xs) is just a named abstraction for the list comprehension [y for x in xs for y in f(x)]. Giving it a name makes the intent clearer and the code more readable.
Compare map (which nests results) with flatmap (which flattens them):
Common use cases include: parsing structured text (splitting lines into words), expanding nested data (extracting all emails from a list of contacts), filtering with transformation (keeping and transforming only valid items), and traversing hierarchies (listing files across multiple directories). The pattern elegantly handles “optional” results too—return an empty list to skip an item, or a single-element list to include it. This avoids the nested lists you’d get from map followed by a separate flatten, and expresses the intent more directly. Below we show a few examples.
Parse CSV-like lines into all values:
Return [] to skip an item, [x] to keep it, or [x, y, ...] to expand it:
dat = [{'emails': ['[email protected]','[email protected]']}, {'emails': []}, {'emails': ['[email protected]']}]
flatmap(~Self['emails'], dat)All files in multiple directories:
[Path('files/test.txt.bz2'),
Path('images/mnist3.png'),
Path('images/att_00000.png'),
Path('images/att_00005.png'),
Path('images/att_00007.png'),
Path('images/att_00006.png'),
Path('images/puppy.jpg')]
Pair each item with its factors:
[(6, 1), (6, 2), (6, 3), (6, 6), (10, 1), (10, 2), (10, 5), (10, 10)]
You can also use kwargs, for instance to apply str.split with a custom separator:
L helpersColBase is a base class that emulates the functionality of a python list:
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, it supports advanced indexing — an int, slice, collection of ints, or boolean mask — and its methods (map, filter, sorted, unique, itemgot, attrgot, and many more) return a new L, encouraging simple expressive chains:
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.
[1, 2, 3, 'j', 4, 'k', 6, 7, 8, 9, 10, 11]
Any L is a Sequence so you can use it with methods like random.sample:
There are optimized indexers for arrays, tensors, and DataFrames.
You can also modify an L with append, +, and *.
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:
len(items)==1 then items is replicated,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).
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.
L implements the __json__ protocol, so xdumps serializes it exactly like a regular list. This works wherever an L appears in the object being encoded.
L MethodsRetrieve idx (can be list of indices, or mask, or int) items
Selections keep the receiver’s type, like every other L operation that builds a new collection (filter, map, sorted, copy), so a subclass keeps its own behavior and display through a slice or a mask:
row 1
row 2
Every method that builds a new collection - selections, filter, map, sorted, copy, +, and the rest - goes through one choke point, _new, so that is where a subclass hooks in. It receives only the resulting items, so anything else the subclass carries has to be copied across explicitly:
class _Titled(L):
def __init__(self, items=None, title=None, **kw):
super().__init__(items, **kw)
self.title = title
def _new(self, items, *args, **kw):
res = super()._new(items, *args, **kw)
res.title = self.title
return res
tl = _Titled(range(5), title='counts')
test_eq(tl[1:3].title, 'counts')
test_eq((tl + [5]).title, 'counts') # one hook covers every derived collection
test_eq(_Titled(range(3))[:2].title, None)State that runs parallel to the items - one entry per item - can’t be carried this way, since _new never learns which items were kept: copying it whole onto a shorter result would be worse than dropping it. Such state belongs on the items themselves.
One name is reserved: L keeps its contents in self.items, so a subclass that assigns to items replaces its own contents rather than adding an attribute, and does so silently. Pick another name for anything a subclass attaches.
The choke point comes with an obligation in the other direction. _new(items) must build a faithful container of the items it is given, and a subclass must not change what indexing or iteration mean: x[i] is x.items[i], and list(x) is list(x.items). The generic methods assume this on both sides, so a subclass whose __getitem__ computes something else (a lazy transform, say) breaks map, filter, and friends in ways that surface far from the cause. Such a class is a view, not a container. Give it an L as an attribute, not as a base class.
To take a plain-L subset of a subclass’s raw items, wrap them: L(x.items, use_list=None)[idx] (use_list=None keeps array-backed items unwrapped, as _new does). Reaching for super().__getitem__ instead depends on the base class never routing through an overridable method, and L makes no such promise.
Set idx (can be list of indices, or mask, or int) items to o (which is broadcast if not iterable)
Unique items, in stable order
Class Method: Same as range, but returns L. Can pass collection for a, to use len(a)
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:
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:
The curryable decorator enables a powerful pattern: methods decorated with it can be called either as instance methods (the normal way) or as class methods that return a partial function.
For instance, consider processing nested data structures. Without curryable, you’d write:
With curryable, you can write:
When you call L.map(int) on the class (not an instance), the decorator returns a functools.partial that waits for an iterable to be passed in later.
This pattern is especially valuable for data parsing pipelines where you’re frequently mapping transformations over nested structures. The curried form reads more naturally and composes well with other curried functions like splitter() and linesplitter().
The curryable methods are map, filter, groupby, argwhere, argfirst, first, last, sorted, reduce, partition, takewhile, dropwhile, and accumulate. Curried forms compose with star too, so to multiply the pairs inside each sublist:
Create new L with f applied to all items, passing args and kwargs to f
Most L methods are also curryable: called on the class instead of an instance, they return a partial awaiting an iterable, so L(lines).map(L.map(int)) converts each nested list. The curryable methods are map, filter, groupby, argwhere, argfirst, first, last, sorted, reduce, partition, takewhile, dropwhile, and accumulate. The star adapter wraps a function to unpack its last argument as individual args, so it works uniformly across these methods (map passes each item last; reduce passes (acc, item), so only the item is unpacked); rstar unpacks in reversed order.
If f is a string then it is treated as a format string to create the mapping:
If f is a dictionary (or anything supporting __getitem__) then it is indexed to create the mapping:
You can also pass the same arg params that bind accepts:
Instead of separate starmap-style methods for every operation, star adapts any function so that its last argument is unpacked as individual arguments. Since it’s the last argument that’s unpacked, it works uniformly across L’s methods: map, filter, and sorted pass each item last, and reduce passes (acc, item), so star(f) there unpacks just the item.
rstar is the same adapter with the unpacked arguments reversed, for when the function expects them in the opposite order to how they’re stored:
Create a partial function that splits strings into L
A curried version of L.split, useful for mapping over collections of strings. For instance to split some lines with the same separator:
[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]
As mentioned in the curryable discussion, map can be curried. This can work well together with L.splitlines output:
Although in this particular example numpy has a useful shortcut:
Create a partial function that splits strings by lines into L
A curried version of L.splitlines, useful for splitting multi-line strings into Ls when mapping over a collection.
L.groupby can also be used in curried form, which is useful when you need to apply the same grouping operation across multiple collections.
[{'a': ['a1', 'a3'], 'b': ['b2']}, {'x': ['x1', 'x3'], 'y': ['y2']}]
Like map, but creates a dict from items to function results
Apply f to zip of items, unpacking each zipped tuple
Create new L with self zip with each of *rest
Apply f to zipwith of items, unpacking each zipped tuple
Create new L filtered by predicate f, passing args and kwargs to f
L.filter can be used as a curried class method, returning a partial that filters any iterable and wraps the result in an L. This is useful when mapping a filter operation over nested collections.
Like filter, but return indices for matching items
Curried L.argfirst returns a partial function that finds the index of the first matching item in any iterable. This is useful when mapping over nested collections to find the first match in each.
Create new L with attr k (or value k for dicts) of all items.
New L sorted by key, using sort_ex. If key is str use attrgetter; if int use itemgetter
Curried L.sorted returns a partial function that sorts any iterable by the given key. This is useful when mapping a sort operation over nested collections—each inner collection gets sorted independently using the same key.
[[(1, 'a'), (2, 'b'), (3, 'c')], [(4, 'd'), (6, 'f')]]
__copy__ makes copy.copy match list semantics: a fresh container over a fresh items list. Without it, the default copy shares items, so appends through the copy would mutate the original.
L.shuffle returns a new shuffled L, leaving the original unchanged:
Curried L.reduce returns a partial function that reduces any iterable using the given function. This is useful when mapping a reduction over nested collections—each inner collection gets reduced independently using the same operation.
E.g implement a dot product:
44
First element of map_filter
Apply f to each element and flatten the results into a single L.
L.flatmap is the method version of the flatmap function, allowing you to call it directly on an L instance. It applies a function to each element and flattens the results into a single L. This is useful for operations where each input naturally produces zero, one, or many outputs.
or alternatively use kwargs:
As an alternative, you can just chain map and concat:
L also wraps the itertools verbs as methods: cycle, takewhile, dropwhile, accumulate, pairwise, batched, compress, permutations, combinations, plus partition (split into two Ls by a predicate) and recursive flatten (strings kept atomic).
L.cycle returns an infinite iterator that cycles through the elements:
L.takewhile returns elements from the beginning of the list while the predicate is true:
Curried L.takewhile returns a partial function that takes elements from the beginning of any iterable while the predicate holds. This is useful when mapping over nested collections—each inner collection gets truncated at the first failing element using the same predicate.
L.dropwhile skips elements from the beginning while the predicate is true, then returns the rest:
Same as itertools.accumulate
L.accumulate returns running totals (or running results of any binary function):
Curried L.accumulate returns a partial function that computes running totals (or running results of any binary function) on any iterable. This is useful when mapping over nested collections—each inner collection gets its own running accumulation using the same function.
[[1, 2, 6], [4, 20, 120], [10, 200]]
L.pairwise returns consecutive overlapping pairs:
Same as itertools.batched (but also works on older Python versions
L.batched splits into chunks of size n:
L.compress filters elements using a boolean selector:
L.permutations returns all permutations of length r (defaults to full length):
L.combinations returns all combinations of length r:
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:
Curried L.partition returns a partial function that splits any iterable into two Ls based on a predicate. This is useful when mapping over nested collections—each inner collection gets partitioned independently using the same predicate, returning a tuple of (true_items, false_items) for each.
[([], [1, 2, 3, 4, 5]), ([10, 15, 20, 25], []), ([6, 9], [3])]
L.flatten recursively flattens nested iterables into a single L. Strings are treated as atomic (not iterated over):
Since star and rstar are plain function adapters, they compose with all of L’s methods, including the curried forms:
test_eq(L((1,2),(3,1),(2,3)).filter(star(lt)), [(1,2),(2,3)])
test_eq(L((3,1),(1,2),(2,0)).sorted(star(operator.sub)), [(1,2),(3,1),(2,0)])
test_eq(L((1,2),(3,4),(5,6)).reduce(star(lambda acc,a,b: acc+a*b), 0), 44)
test_eq(L((2,1),(3,2),(1,4)).takewhile(rstar(lt)), [(2,1),(3,2)])
test_eq(L([[(1,2),(3,4)], [(5,6)]]).map(L.map(star(operator.mul))), [[2,12],[30]])