Comparison¶
How pipef’s syntax lines up against other function-piping libraries already on PyPI, and where it
actually differs rather than just looking different
All examples below pipe 1 through add_2 then mul_3, expecting 9
Reusable composed callable¶
pipef’s lazy mode builds a function once and calls it later
fn = pipef | add_2 | mul_3
fn(1)
Library |
Equivalent |
Notes |
|---|---|---|
|
near-identical shape; |
|
|
preserves introspection and pickling, which |
|
|
no |
|
|
every stage needs its own |
Eager value through a chain¶
pipef’s eager mode applies each step immediately and hands back the result
result, = pipef(1) | add_2 | mul_3
Library |
Equivalent |
Notes |
|---|---|---|
none |
build the chain, then call it — there’s no true eager form |
|
|
returns the value directly, no unpack needed |
|
|
|
|
|
a function call, not |
|
|
n/a |
built for lazily filtering/mapping iterables ( |
The result, = unpack is the price pipef pays for one property nothing else surveyed offers: see
Multi-arg seed below
Multi-arg seed¶
Piping pipef(1, 2, c=3) seeds the chain with positional and keyword arguments up front, before any
function runs
result, = pipef(1, 2, c=3) | add_all | mul_3
Library |
Form |
Result |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
No library surveyed accepts arguments at the seed the way pipef does. pipetools and compose-operator
reach the same result by moving the arguments to the end of the expression instead, without needing
result, = — so this is a difference in where the arguments go, not in what’s possible
Function factory branching¶
The Function Factory (pipef’s lazy mode) forks into new reusable functions without mutating the
original — piping off an already-built chain returns a fresh one, and the base chain stays callable on
its own
base = pipef | f1
branch_a = base | f2
branch_b = base | f3
branch_a, branch_b, and base are all independently callable afterward, and base(x) still only runs
f1
Library |
Model |
Base mutated? |
|---|---|---|
|
|
no — matches |
|
|
no — matches |
|
|
no — matches |
Unlike eager branching below, this isn’t a real differentiator. Nothing in a lazy chain runs until the
final call, so forking one is cheap by construction in every library here — there’s simply nothing to
re-execute yet. pipetools verifies this identically: __or__ never assigns to self.func, it returns a
new Pipe wrapping a new composite, leaving the original untouched and still callable. Where pipef
actually pulls ahead is in eager mode, where branching forces a real choice between rerunning a shared
prefix or not
Advantages, and when to just use what you have¶
pipef’s case in one sentence: one | operator, one import, that covers both the reusable-function shape
and the eager-value shape, with the branch sharing and multi-arg seed above thrown in — and no
dependencies to add
That said, none of this is worth a migration on its own. If a project already pipes through pipetools,
toolz, sspipe, or anything else in the tables above and it reads fine, keep using it — the syntax
differences here are mostly a matter of taste, and rewriting working pipelines to save an import isn’t a
trade worth making. pipef is aimed at new code, or at a project that wants both shapes without pulling in
two different libraries to get them