Skip to main content

Lightweight functional-style helpers for Python 3.13+

Project description

fnutil

Lightweight functional-style helpers for Python 3.13+.

Provides two composable primitives:

  • Expression[T] — wraps a single value in a chainable, Option-aware monad with arithmetic and comparison operators.
  • Iterator[T] — wraps any iterable with a lazy, chainable adapter API modelled on Rust's Iterator trait.

Installation

pip install fnutil
uv add fnutil

Quick start

from fnutil import expr, it

# Expression: wrap → transform → unwrap
result = (
    expr(10)
    .map(lambda x: x * 2)
    .filter(lambda x: x > 15)
    .unwrap_or(0)
)
assert result == 20

# Iterator: wrap → chain lazy adapters → collect
total = (
    it(range(10))
    .filter(lambda x: x % 2 == 0)
    .map(lambda x: x ** 2)
    .fold(0, lambda acc, x: acc + x)
)
assert total == 120

Expression[T]

from fnutil import expr

Construction

e = expr(42)        # Expression[int]
e = expr(None)      # Expression[None]
e = expr([1, 2, 3]) # Expression[list[int]]

Transform

Method Description
.map(fn) Apply fn to the value; return Expression[U]
.flat_map(fn) Apply fn which returns Expression[U]; unwrap one level
.pipe(f, g, h) Thread value through functions left-to-right; equivalent to expr(h(g(f(x))))
.then(fn) Apply fn and return the raw result (exits the monad)
.inspect(fn) Call fn for side-effects; return self unchanged

Option-like

Method / Property Description
.is_some True when value is not None
.is_none True when value is None
.filter(fn) Return self if fn(value) is truthy, else Expression(None)
.or_else(default) Return self when is_some, else Expression(default)
.or_else_with(fn) Return self when is_some, else Expression(fn())
.unwrap_or(default) Return inner value when is_some, else default (raw)

Iterator interop

expr([1, 2, 3]).iter()   # → Iterator[int]
expr(range(5)).iter()    # → Iterator[int]

Operators

Arithmetic and comparison operators delegate to the inner value. Both operands must be Expression instances.

expr(3) + expr(4)   # Expression(7)
expr(10) / expr(4)  # Expression(2.5)
expr(2) ** expr(8)  # Expression(256)
-expr(5)            # Expression(-5)
abs(expr(-7))       # Expression(7)

expr(1) < expr(2)   # True
expr(3) >= expr(3)  # True
sorted([expr(3), expr(1), expr(2)])  # [Expression(1), Expression(2), Expression(3)]

Equality

Expression equality compares inner values. Comparing with a raw value returns False.

expr(1) == expr(1)  # True
expr(1) == 1        # False

Iterator[T]

from fnutil import it

Construction

it([1, 2, 3])
it(range(10))
it(x for x in some_generator())

Lazy adapters (return Iterator[T])

Method Description
.filter(fn) Keep elements where fn is truthy
.map(fn) Transform each element
.enumerate() Yield (index, element) tuples
.zip(other) Zip with another iterable (truncates to shortest)
.zip_longest(other, fillvalue=None) Zip, padding the shorter side
.chain(other) Concatenate another iterable
.take(n) First n elements
.skip(n) Skip first n elements
.take_while(fn) Take elements while fn is truthy
.skip_while(fn) Skip elements while fn is truthy
.flat_map(fn) Map fn then flatten one level
.flatten() Flatten one level of nesting
.inspect(fn) Side-effect on each element; pass through unchanged
.filter_false(fn) Keep elements where fn is falsy
.accumulate(fn=None, *, initial=None) Running accumulated values
.cycle() Repeat endlessly
.batched(n) Non-overlapping tuples of length n
.pairwise() Successive overlapping pairs
.compress(selectors) Keep elements whose selector is truthy
.starmap(fn) Unpack each element as fn(*item)
.group_by(fn=None) Group consecutive elements by key
[i] / [start:stop:step] Integer index (take) or slice (islice)

Consuming terminators (exhaust and return a value)

Method Returns Description
.collect(factory) U e.g. .collect(list), .collect(set)
.collect_expr(factory) Expression[U] Same, wrapped in Expression
.fold(init, fn) U Left-fold with initial value
.reduce(fn) T | None Left-fold without init; None on empty
.for_each(fn) None Consume, calling fn on each element
.count() int Number of elements
.find(fn) T | None First element where fn is truthy
.any(fn) bool True if any element satisfies fn
.all(fn) bool True if all elements satisfy fn
.min() T | None Minimum element; None if empty
.max() T | None Maximum element; None if empty
.nth(n) T | None Element at zero-based index n; None if out of range
.last() T | None Last element; None if empty
.partition(fn) (list[T], list[T]) (truthy, falsy) split
.value Iterable[T] Raw underlying iterable

Examples

from fnutil import it

# Collect into a list
it([1, 2, 3]).map(lambda x: x * 2).collect(list)
# [2, 4, 6]

# Group consecutive words by first letter
words = ["ant", "bee", "bat", "cat"]
for letter, group in it(sorted(words)).group_by(lambda w: w[0]):
    print(letter, group.collect(list))
# a ['ant']
# b ['bat', 'bee']
# c ['cat']

# Batch into chunks
it(range(7)).batched(3).collect(list)
# [(0, 1, 2), (3, 4, 5), (6,)]

# Combine with Expression
it(range(10)).filter(lambda x: x % 2 == 0).collect_expr(list)
# Expression([0, 2, 4, 6, 8])

Type checking

fnutil ships a py.typed marker and .pyi stub files for all modules. It is fully compatible with Pyright and mypy.

Requirements

  • Python 3.13+
  • No runtime dependencies

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fnutil-1.0.1.tar.gz (8.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fnutil-1.0.1-py3-none-any.whl (11.1 kB view details)

Uploaded Python 3

File details

Details for the file fnutil-1.0.1.tar.gz.

File metadata

  • Download URL: fnutil-1.0.1.tar.gz
  • Upload date:
  • Size: 8.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.22

File hashes

Hashes for fnutil-1.0.1.tar.gz
Algorithm Hash digest
SHA256 961104b37136d4f13c6c9d179ad8ff4286b7e81c3a24af8a6b76fc1c0472547b
MD5 e6bf3f37ca8fd98e7055549f9e9f93a9
BLAKE2b-256 5c467261cf2cc47775ab509b379d3ac366b4a1d3ce46983b651bfc67237fb6eb

See more details on using hashes here.

File details

Details for the file fnutil-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: fnutil-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 11.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.22

File hashes

Hashes for fnutil-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0993d3cc11fe6e4893d5bbb7c0d1e7fa887179d38f297a56e651da6130d140d3
MD5 4de039c183458f5e5c8064c6dd22a182
BLAKE2b-256 7790d3833733ca528b85e0aba3e7f08605b32f369ee2051fe01b395572117dec

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page