1. Proposal:
class pipe:
def __init__(self, *funcs):
...
def __call__(self, obj, /, *args, **kwds):
...
def append(self, *funcs, inplace=True):
...
# No arg case
identity = pipe()
identity(1) # 1
signature(identity) # functools.pipe(x)
# First member function can have any signature
def func(foo, bar):
return foo + bar
new_func = pipe(func, lambda x: x + 3)
signature(new_func) # functools.pipe(foo, bar)
new_func(1, 2) # 6
It is a fairly simple addition.
It would probably be the simplest component in the functools
module.
2. Applications
a) Missing identity function (@sayandipdutta’s idea)
Such does not exist in standard library:
- github
/lambda x: x( )?(\)|,)/
- 128k files
# No argument pipeline acts as identity function
pipeline = pipe()
pipe(1) # 1
I am not a big fan defining lambda for identity, knowing it will be one extra function call. And identity functions tend to be used in places that are being called over and over. I suspect it would be ~2x faster than lambda.
b) function composition / iterator recipes
Would speed up a certain portion of iterator function calls with predicates and keys:
E.g.
lst = [{'a': [2]}, {'a': [1]}, {'a': [0]}]
pred = pipe(itemgetter('a'), itemgetter(0))
list(filter(pred, lst))
# [{'a': [2]}, {'a': [1]}]
sorted(lst, key=pred)
# [{'a': [0]}, {'a': [1]}, {'a': [2]}]
From my experience, there is a certain amount of iterator recipes that are not currently competitive. But they would, given efficient function composition.
Also, it is sometimes the case that using function composition would make code a bit more intuitive than alternatives that make use iterator
functions in ingenious, but involved ways.
c) convenient piping
I think this is a missing component in stdlib to cover the basics for efficient and convenient function composition. It would allow slightly-less-elegant-than-some-of-ideas code to achieve: funnel operator
With 2 minimal inherited classes, user can enjoy a fairly pleasant syntax that allows sufficient flexibility for a large part of use cases. And can choose operators to one’s liking (or not use them at all).
class pipe(functools.pipe):
__ror__ = functools.pipe.__call__
__rshift__ = functools.pipe.append
class prtl:
def __init__(self, *args, **kwds):
self.args = args
self.kwds = kwds
def __call__(self, func):
return functools.partial(func, *self.args, **self.kwds)
__rmatmul__ = __call__
result = 1 | (pipe()
>> opr.sub@prtl(_, 2)
>> opr.neg
>> opr.contains@prtl({1: 'a'})
)
result # 'a'
pipeline = pipe(
opr.sub@prtl(_, 2),
opr.neg,
opr.contains@prtl({1: 'a'})
)
# Or
pipeline = (pipe()
>> opr.sub@prtl(_, 2)
>> opr.neg
>> opr.contains@prtl({1: 'a'})
)
pipeline(1) # 'a'
3. PyPI:
- function-pipes · PyPI
- function-pipe · PyPI
- functional-pipeline · PyPI
- GitHub - JulienPalard/Pipe: A Python library to use infix notation in Python
- funcpipe · PyPI
All of the above are written in pure python.
Some of them are simple, some are complex.
IMO the above solution has nicer syntax than most of those.
Furthermore, it would be more efficient (with the exception with the one that attempts to create new function by parsing ast
s)