Nicer iterator patterns for Python! Chain map, filter, zip, unzip, cycle, skip and scan like there is no tomorrow.
Install with
pip install fluentiter
With fluentiter you can do this
lines = iterator(haystack_csv.split("\n"))
header = [x for x in lines.next().split(",") if x.strip(" ")]
needle = (
lines.map(lambda line: line.split(","))
.map(lambda values: {k: v for k, v in zip(header, values)})
.filter(lambda value_dct: value_dct["material"] != "hay")
.find(lambda value_dct: value_dct["type"] == "needle")
)
instead of this
lines = iter(some_csv.split("\n"))
header = [x for x in next(lines).split(",") if x.strip()]
needle = next(
filter(
lambda value_dct: value_dct["material"] != "hay",
filter(
lambda value_dct: value_dct["type"] == "needle",
map(
lambda values: {k: v for k, v in zip(header, values)},
map(lambda line: line.split(","), lines),
),
),
)
)
See the this example for a more complete version.
To me Pythons iterator functions (i.e. map
, filter
, zip
, etc.) always seemed backward.
While generator expressions and list/dict/set comprehensions give you nice left-to-right readability,
map
and filter
force you to read "inside-out":
[func(x) for x in something if x == y]
map(func, filter(lambda x: x == y, something))
This is not an issue for short an simple statements, as they can be written concisely using expressions, but chaining multiple operations, will result in either many intermediate variable assignements or serious spaghettification.
Any Iterable
can be turned into a FluentIterator
by just passing it to the iterator
function:
from fluentiter import iterator
bugs = ["john", "paul", "ringo", "george"]
fluent = iterator(bugs)
# you can now do
# fluent.map(...).filter(...).cycle(...).scan(...) and so on
The FluentIterator
provides a rich set of methods you can call and chain together to your liking:
Of course there are the classic map
, filter
, and reduce
functions, but also some more really useful features like cycle
to repeat an iterator forever, find
to find an element or even partition
to turn your iterator into two.
In total FluentIterator
provides 37 methods to compose beautiful and easy to follow iteration
patterns.
Check the API docs to see them all.
- 37 cool fresh iterator methods
- 100% Type annotated e.g.
iterator(["foo", "bar"]).map(len).to_list()
gets correctly inferred aslist[int]
- 100% Test coverage
- 0 dependencies outside the Python standard library1
The simplest way to contribute is to open an issue. If you would like to see some feature implemented or found a bug, head over to the issues section.
- Clone the repository
- Install Poetry
- Install development dependencies with
poetry install
- Enter the poetry shell with
poetry shell
Your PR should include relevant tests for the changes you are contributing and be fully type annotated. If you are unsure or stuck, please open the PR anyway and we can work it out together.
By opening a PR you agree to your code becoming part of the fluentiter package and being published under fluentiters license.
- Formatting:
poe format
- Linting:
poe lint
- Tests:
poe test
All of the above: poe all
Running coverage html
will create a file htmlcov/index.html
you can open to view the test coverage report
- 1.0.0
- initial release
- 1.0.1
- fixed errors int readme
- 1.1.0
- add
into(...)
method
- add
- 1.1.1
- add
py.typed
marker file
- add
- 1.2.0
- add
tumbling_window
function - fix mypy inference on
.flatten()
and.flat_map
- fix
more_itertools
being installed automatically
- add
- 1.2.1
- fix parameter key not being respected in
.min
and.max
- fix parameter key not being respected in
Thank you to all Rust maintainers for creating the Iterator trait, which served as the main source of inspiration.
Footnotes
-
Some functions may require the
more
extra which has more-itertools as a dependency ↩