Daniel Roy Greenfeld

Daniel Roy Greenfeld

About | Articles | Books | Jobs | Tags | Search

Fastcore L

An exploration of Fastcore's enhanced list-like structure

In September of 2023 after a 5 year hiatus from creating new Python packages I decided to re-explore how to package Python code for distribution. This was before [uv](https://docs.astral.sh/uv/) existed so it was worth knowing all the nitty-gritty details. My sample project was [listo](https://github.com/pydanny/listo), an enhanced list for Python with these planned features: - More consistent behavior . No more methods with "in-place" changes. Every method call returns a value - Added methods that are missing from the list type, like `.map()` and `.filter()`, which I never got around to implementing It was a fun little exploratory package. Alas, at the time my open source efforts went more towards [dj-notebook](https://github.com/pydanny/dj-notebook). Nevertheless I sometimes thought about revisiting and finishing the project. Then I discovered `fastcore.foundation.L`. This handy sequence tool is similar in concept to my own `listo` but much more complete. Let's go over some of its features and compare it to the standard Python `list` structure. ## Basic usage ```python {.marimo} from fastcore.foundation import L ``` Let's create a L sequence and example it: ```python {.marimo} lst1 = L(range(100)) lst1 ``` The print representation of an L sequence has two interesting features. For starters, it provides a count of items, in this case 100 of them. It also truncates the output so if you have a large sequence you aren't given an oversized block of values. Like `listo()`, every method provided by `L` returns an `L` object. This allows for chaining of operations. Let's see what that works with a list of duplicated values. ```python {.marimo} lst2 = L(list(range(5))+list(range(5))) lst2 ``` We'll remove duplicates while preserving order and keeping only even values. ```python {.marimo} lst2.unique().filter(lambda x: not x % 2 and x) ``` This line of code is using two L methods: - `.unique()` for removing duplicate elements but maintaining the order of the sequence - `.filter()` for running a function that based on a boolean response keeps or removes values. In this case we use a lambda that returns a boolean to tell it to only keep even values While I could do this with a regular Python list it's bit more verbose. ```python {.marimo} seen = set() [x for x in lst2 if not (x in seen or seen.add(x)) if not x % 2 and x] ``` While I love regular expressions and use them constantly, I have to say that L is more legible. Take a look at both and you'll see what I mean: - `lst2.unique().filter(lambda x: not x % 2 and x)` - `seen = set(); [x for x in lst2 if not (x in seen or seen.add(x)) if not x % 2 and x]` ## Why not use L for every sequence? There's no one-size fits all structure for sequences. Here's two reasons why it's good to keep up your list comprehension skills. 1. `Fastcore` is another Python dependency, sometimes you can't control the libraries you are using. This is why list comprehension is such an important skill for any Python dev. 2. `L` is not a generator expression, meaning large sizes in terms of number of elements or the size of individual elements can be problematic. The ability to convert a list comprehension like `[x for x in range(100)]` to a generator expression like `(x for x in range(100))` simply by converting square brackets to parenthesis is one of Python's greatest superpowers ```python {.marimo hide_code="true"} mo.md( r""" """ ) ``` ```python {.marimo} import marimo as mo ```

Tags: python fastcore howto

← Back to all articles